diff --git a/package.json b/package.json index 0d6aa0ece..e8acb41b1 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,11 @@ "date-fns": "^2.28.0", "date-fns-tz": "^1.2.2", "eta": "^1.12.3", - "framer-motion": "^6.2.9", + "framer-motion": "^6.3.11", "iron-session": "^6.1.3", "jose": "^4.5.1", "js-cookie": "^3.0.1", "lodash": "^4.17.21", - "mongodb": "^4.5.0", "nanoid": "^3.1.30", "next": "^12.1.4", "next-i18next": "^10.5.0", @@ -60,7 +59,7 @@ "zod": "^3.16.0" }, "devDependencies": { - "@playwright/test": "^1.20.1", + "@playwright/test": "^1.22.2", "@types/lodash": "^4.14.178", "@types/nodemailer": "^6.4.4", "@types/react": "^17.0.5", diff --git a/prisma/migrations/20220623175037_remove_links_model/migration.sql b/prisma/migrations/20220623175037_remove_links_model/migration.sql new file mode 100644 index 000000000..7706e7a9f --- /dev/null +++ b/prisma/migrations/20220623175037_remove_links_model/migration.sql @@ -0,0 +1,26 @@ +-- AlterTable +ALTER TABLE "polls" +ADD COLUMN "admin_url_id" TEXT, +ADD COLUMN "participant_url_id" TEXT; + +UPDATE polls + SET participant_url_id=(SELECT url_id FROM links WHERE polls.url_id=links.poll_id AND links."role"='participant'); + +UPDATE polls + SET admin_url_id=(SELECT url_id FROM links WHERE polls.url_id=links.poll_id AND links."role"='admin'); + +ALTER TABLE "polls" +ALTER COLUMN "admin_url_id" SET NOT NULL, +ALTER COLUMN "participant_url_id" SET NOT NULL; + +-- DropTable +DROP TABLE "links"; + +-- DropEnum +DROP TYPE "role"; + +-- CreateIndex +CREATE UNIQUE INDEX "polls_participant_url_id_key" ON "polls"("participant_url_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "polls_admin_url_id_key" ON "polls"("admin_url_id"); diff --git a/prisma/migrations/20220624111614_rename_poll_id/migration.sql b/prisma/migrations/20220624111614_rename_poll_id/migration.sql new file mode 100644 index 000000000..3520eaef6 --- /dev/null +++ b/prisma/migrations/20220624111614_rename_poll_id/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - The primary key for the `polls` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `url_id` on the `polls` table. All the data in the column will be lost. + - A unique constraint covering the columns `[id]` on the table `polls` will be added. If there are existing duplicate values, this will fail. + - Added the required column `id` to the `polls` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropIndex +DROP INDEX "Poll_urlId_key"; + +-- DropIndex +DROP INDEX "polls_url_id_key"; + +-- AlterTable +ALTER TABLE "polls" +RENAME COLUMN "url_id" TO "id"; + +-- CreateIndex +CREATE UNIQUE INDEX "polls_id_key" ON "polls"("id"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 123801f87..f876bffb7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,11 +1,11 @@ datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") referentialIntegrity = "prisma" } generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" previewFeatures = ["referentialIntegrity"] } @@ -29,60 +29,43 @@ enum PollType { } model Poll { - urlId String @id @unique @map("url_id") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - deadline DateTime? - title String - type PollType - description String? - location String? - user User @relation(fields: [userId], references: [id]) - userId String @map("user_id") - votes Vote[] - timeZone String? @map("time_zone") - verified Boolean @default(false) - options Option[] - participants Participant[] - authorName String @default("") @map("author_name") - demo Boolean @default(false) - comments Comment[] - links Link[] - legacy Boolean @default(false) - closed Boolean @default(false) - notifications Boolean @default(false) - deleted Boolean @default(false) - deletedAt DateTime? @map("deleted_at") - touchedAt DateTime @default(now()) @map("touched_at") + id String @id @unique @map("id") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deadline DateTime? + title String + type PollType + description String? + location String? + user User @relation(fields: [userId], references: [id]) + userId String @map("user_id") + votes Vote[] + timeZone String? @map("time_zone") + verified Boolean @default(false) + options Option[] + participants Participant[] + authorName String @default("") @map("author_name") + demo Boolean @default(false) + comments Comment[] + legacy Boolean @default(false) + closed Boolean @default(false) + notifications Boolean @default(false) + 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") @@map("polls") } -enum Role { - admin - participant - - @@map("role") -} - -model Link { - urlId String @id @unique @map("url_id") - role Role - pollId String @map("poll_id") - poll Poll @relation(fields: [pollId], references: [urlId]) - createdAt DateTime @default(now()) @map("created_at") - - @@unique([pollId, role]) - @@map("links") -} - model Participant { id String @id @default(cuid()) name String user User? @relation(fields: [userId], references: [id]) userId String? @map("user_id") guestId String? @map("guest_id") - poll Poll @relation(fields: [pollId], references: [urlId]) + poll Poll @relation(fields: [pollId], references: [id]) pollId String @map("poll_id") votes Vote[] createdAt DateTime @default(now()) @map("created_at") @@ -96,7 +79,7 @@ model Option { id String @id @default(cuid()) value String pollId String @map("poll_id") - poll Poll @relation(fields: [pollId], references: [urlId]) + poll Poll @relation(fields: [pollId], references: [id]) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime? @updatedAt @map("updated_at") votes Vote[] @@ -118,7 +101,7 @@ model Vote { participantId String @map("participant_id") option Option @relation(fields: [optionId], references: [id], onDelete: Cascade) optionId String @map("option_id") - poll Poll @relation(fields: [pollId], references: [urlId]) + poll Poll @relation(fields: [pollId], references: [id]) pollId String @map("poll_id") type VoteType @default(yes) createdAt DateTime @default(now()) @map("created_at") @@ -130,7 +113,7 @@ model Vote { model Comment { id String @id @default(cuid()) content String - poll Poll @relation(fields: [pollId], references: [urlId]) + poll Poll @relation(fields: [pollId], references: [id]) pollId String @map("poll_id") authorName String @map("author_name") user User? @relation(fields: [userId], references: [id]) diff --git a/public/locales/en/app.json b/public/locales/en/app.json index 349eb3a90..dbfec42dd 100644 --- a/public/locales/en/app.json +++ b/public/locales/en/app.json @@ -20,7 +20,7 @@ "calendarHelp": "You can't create a poll without any options. Add at least one option to continue.", "errorCreate": "Uh oh! There was a problem creating your poll. The error has been logged and we'll try to fix it.", "share": "Share", - "shareDescription": "This poll is open to anyone who has the following link:", + "shareDescription": "Give this link to your participants to allow them to vote on your poll.", "requiredNameError": "Name is required", "remove": "Remove", "change": "Change", @@ -38,9 +38,7 @@ "loading": "Loading…", "loadingParticipants": "Loading participants…", "admin": "Admin", - "adminDescription": "Full access to edit this poll.", "participant": "Participant", - "participantDescription": "Partial access to vote and comment on this poll.", "unverifiedMessage": "An email has been sent to {{email}} with a link to verify the email address.", "notificationsOnDescription": "An email will be sent to {{email}} when there is activity on this poll.", "deletingOptionsWarning": "You are deleting options that participants have voted for. Their votes will be also be deleted.", diff --git a/public/locales/en/homepage.json b/public/locales/en/homepage.json index b1559fb18..056e6eba4 100644 --- a/public/locales/en/homepage.json +++ b/public/locales/en/homepage.json @@ -1,5 +1,5 @@ { "getStarted": "Get started", - "viewDemo": "View demo", + "viewDemo": "Live demo", "footerCredit": "Self-funded and built by @imlukevella" } diff --git a/src/components/badge.tsx b/src/components/badge.tsx index 4b452e260..0fb963c41 100644 --- a/src/components/badge.tsx +++ b/src/components/badge.tsx @@ -9,7 +9,7 @@ const Badge: React.VoidFunctionComponent<{ return (
= ({ const plausible = usePlausible(); const createPoll = trpc.useMutation(["polls.create"], { - onSuccess: (poll) => { + onSuccess: (res) => { setIsRedirecting(true); plausible("Created poll", { props: { @@ -104,7 +104,7 @@ const Page: NextPage = ({ }, }); setPersistedFormData(initialNewEventData); - router.replace(`/admin/${poll.urlId}`); + router.replace(`/admin/${res.urlId}?sharing=true`); }, }); diff --git a/src/components/discussion/discussion.tsx b/src/components/discussion/discussion.tsx index 7c4818613..1fcaaed16 100644 --- a/src/components/discussion/discussion.tsx +++ b/src/components/discussion/discussion.tsx @@ -27,9 +27,9 @@ interface CommentForm { const Discussion: React.VoidFunctionComponent = () => { const { locale } = usePreferences(); const queryClient = trpc.useContext(); - const { - poll: { pollId }, - } = usePoll(); + const { poll } = usePoll(); + + const pollId = poll.id; const { data: comments } = trpc.useQuery( ["polls.comments.list", { pollId }], @@ -53,8 +53,6 @@ const Discussion: React.VoidFunctionComponent = () => { }, }); - const { poll } = usePoll(); - const deleteComment = trpc.useMutation("polls.comments.delete", { onMutate: ({ commentId }) => { queryClient.setQueryData( @@ -96,9 +94,7 @@ const Discussion: React.VoidFunctionComponent = () => { {comments.map((comment) => { const canDelete = - poll.role === "admin" || - session.ownsObject(comment) || - isUnclaimed(comment); + poll.admin || session.ownsObject(comment) || isUnclaimed(comment); return ( { )}
- {canDelete ? ( - } - > - { - deleteComment.mutate({ - commentId: comment.id, - pollId, - }); - }} - /> - - ) : null} + } + > + { + deleteComment.mutate({ + commentId: comment.id, + pollId, + }); + }} + /> +
{comment.content} @@ -187,11 +182,7 @@ const Discussion: React.VoidFunctionComponent = () => { )} />
- diff --git a/src/components/home/poll-demo.tsx b/src/components/home/poll-demo.tsx index 9a5922e35..3f801ee49 100644 --- a/src/components/home/poll-demo.tsx +++ b/src/components/home/poll-demo.tsx @@ -87,6 +87,7 @@ const PollDemo: React.VoidFunctionComponent = () => { color={participant.color} sidebarWidth={sidebarWidth} columnWidth={columnWidth} + participantId={`participant${i}`} name={participant.name} votes={options.map((_, i) => { return participant.votes.some((vote) => vote === i) ? "yes" : "no"; diff --git a/src/components/icons/key.svg b/src/components/icons/key.svg new file mode 100644 index 000000000..1f9a1da4c --- /dev/null +++ b/src/components/icons/key.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/poll-context.tsx b/src/components/poll-context.tsx index f7aa42c5e..6e44f8e6a 100644 --- a/src/components/poll-context.tsx +++ b/src/components/poll-context.tsx @@ -20,7 +20,10 @@ import { useRequiredContext } from "./use-required-context"; type PollContextValue = { userAlreadyVoted: boolean; poll: GetPollApiResponse; + urlId: string; + admin: boolean; targetTimeZone: string; + participantUrl: string; setTargetTimeZone: (timeZone: string) => void; pollType: "date" | "timeSlot"; highScore: number; @@ -49,9 +52,11 @@ export const usePoll = () => { }; export const PollContextProvider: React.VoidFunctionComponent<{ - value: GetPollApiResponse; + poll: GetPollApiResponse; + urlId: string; + admin: boolean; children?: React.ReactNode; -}> = ({ value: poll, children }) => { +}> = ({ poll, urlId, admin, children }) => { const { participants } = useParticipants(); const [isDeleted, setDeleted] = React.useState(false); const { user } = useSession(); @@ -129,10 +134,17 @@ export const PollContextProvider: React.VoidFunctionComponent<{ ); }); + const { participantUrlId } = poll; + + const participantUrl = `${window.location.origin}/p/${participantUrlId}`; + return { optionIds, userAlreadyVoted, poll, + urlId, + admin, + participantUrl, getParticipantById: (participantId) => { return participantById[participantId]; }, @@ -152,7 +164,17 @@ export const PollContextProvider: React.VoidFunctionComponent<{ isDeleted, setDeleted, }; - }, [getScore, isDeleted, locale, participants, poll, targetTimeZone, user]); + }, [ + admin, + getScore, + isDeleted, + locale, + participants, + poll, + targetTimeZone, + urlId, + user, + ]); if (isDeleted) { return ( diff --git a/src/components/poll.tsx b/src/components/poll.tsx index c7d295bb0..f7dabdc31 100644 --- a/src/components/poll.tsx +++ b/src/components/poll.tsx @@ -1,13 +1,14 @@ +import { AnimatePresence, motion } from "framer-motion"; import { NextPage } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; import { usePlausible } from "next-plausible"; import React from "react"; import toast from "react-hot-toast"; import { useMount } from "react-use"; import { Button } from "@/components/button"; -import LocationMarker from "@/components/icons/location-marker.svg"; import LockClosed from "@/components/icons/lock-closed.svg"; import Share from "@/components/icons/share.svg"; import { preventWidows } from "@/utils/prevent-widows"; @@ -19,11 +20,11 @@ import { useUpdatePollMutation } from "./poll/mutations"; import NotificationsToggle from "./poll/notifications-toggle"; import PollSubheader from "./poll/poll-subheader"; import TruncatedLinkify from "./poll/truncated-linkify"; +import { UnverifiedPollNotice } from "./poll/unverified-poll-notice"; import { useTouchBeacon } from "./poll/use-touch-beacon"; import { UserAvatarProvider } from "./poll/user-avatar"; import VoteIcon from "./poll/vote-icon"; import { usePoll } from "./poll-context"; -import Popover from "./popover"; import { useSession } from "./session"; import Sharing from "./sharing"; import StandardLayout from "./standard-layout"; @@ -34,19 +35,13 @@ const DesktopPoll = React.lazy(() => import("@/components/poll/desktop-poll")); const MobilePoll = React.lazy(() => import("@/components/poll/mobile-poll")); const PollPage: NextPage = () => { - const { poll } = usePoll(); + const { poll, urlId, admin } = usePoll(); const { participants } = useParticipants(); const router = useRouter(); - useTouchBeacon(poll.pollId); + useTouchBeacon(poll.id); - useMount(() => { - const path = poll.role === "admin" ? "admin" : "p"; - - if (!new RegExp(`^/${path}`).test(router.asPath)) { - router.replace(`/${path}/${poll.urlId}`, undefined, { shallow: true }); - } - }); + const { t } = useTranslation("app"); const session = useSession(); @@ -58,7 +53,7 @@ const PollPage: NextPage = () => { const verifyEmail = trpc.useMutation(["polls.verification.verify"], { onSuccess: () => { toast.success("Your poll has been verified"); - queryClient.setQueryData(["polls.get", { urlId: poll.urlId }], { + queryClient.setQueryData(["polls.get", { urlId, admin }], { ...poll, verified: true, }); @@ -78,14 +73,14 @@ const PollPage: NextPage = () => { useMount(() => { const { code } = router.query; if (typeof code === "string" && !poll.verified) { - verifyEmail.mutate({ code, pollId: poll.pollId }); + verifyEmail.mutate({ code, pollId: poll.id }); } }); React.useEffect(() => { if (router.query.unsubscribe) { updatePollMutation( - { urlId: poll.urlId, notifications: false }, + { urlId: urlId, notifications: false }, { onSuccess: () => { toast.success("Notifications have been disabled"); @@ -97,7 +92,7 @@ const PollPage: NextPage = () => { shallow: true, }); } - }, [plausible, poll.urlId, router, updatePollMutation]); + }, [plausible, urlId, router, updatePollMutation]); const checkIfWideScreen = () => window.innerWidth > 640; @@ -120,10 +115,13 @@ const PollPage: NextPage = () => { [participants], ); + const [isSharingVisible, setSharingVisible] = React.useState( + !!router.query.sharing, + ); return ( - + -
+
{poll.title} @@ -134,92 +132,144 @@ const PollPage: NextPage = () => { width: Math.max(768, poll.options.length * 95 + 200 + 160), }} > -
-
-
-
-

+
+ + + +
+ + {isSharingVisible ? ( + - {preventWidows(poll.title)} -

- {poll.role === "admin" ? ( -
- - -
- }> - Share - - } - placement={isWideScreen ? "bottom-end" : undefined} - > - - -
-
- ) : null} + { + setSharingVisible(false); + router.replace( + `/admin/${router.query.urlId}`, + undefined, + { + shallow: true, + }, + ); + }} + /> + + ) : null} + + {poll.verified === false ? ( +
+
- + ) : null} + + ) : null} + {!poll.admin && poll.adminUrlId ? ( +
+
+ Hey {poll.user.name}, looks like you are the owner of this + poll.
-
- {poll.description ? ( -
- - {preventWidows(poll.description)} - -
- ) : null} - {poll.location ? ( -
-
- -
- {poll.location} -
- ) : null} -
- {poll.closed ? ( -
-
- -
- This poll has been locked (voting is disabled) + + Go to admin → +
) : null} - -
- Key: - - - Yes - - - - If need be - - - - - No - -
- Loading…
}> - {participants ? ( -
- + {poll.closed ? ( +
+
+
- ) : null} +
+
This poll has been locked
+
+
+ ) : null} +
+
+
+
+
+ {preventWidows(poll.title)} +
+ +
+ {poll.description ? ( +
+ + {preventWidows(poll.description)} + +
+ ) : null} + {poll.location ? ( +
+
+ {t("location")} +
+ {poll.location} +
+ ) : null} +
+
+ Possible answers +
+
+ + + Yes + + + + + If need be + + + + + No + +
+
+
+
+ + {participants ? : null} + +
+ + Loading…
}>
diff --git a/src/components/poll/desktop-poll.tsx b/src/components/poll/desktop-poll.tsx index 18cca5335..f3bdd00c5 100644 --- a/src/components/poll/desktop-poll.tsx +++ b/src/components/poll/desktop-poll.tsx @@ -7,7 +7,6 @@ import smoothscroll from "smoothscroll-polyfill"; import { Button } from "../button"; import ArrowLeft from "../icons/arrow-left.svg"; import ArrowRight from "../icons/arrow-right.svg"; -import PlusCircle from "../icons/plus-circle.svg"; import { useParticipants } from "../participants-provider"; import { usePoll } from "../poll-context"; import TimeZonePicker from "../time-zone-picker"; @@ -23,29 +22,25 @@ if (typeof window !== "undefined") { const MotionButton = motion(Button); -const MotionParticipantFormRow = motion(ParticipantRowForm); - -const minSidebarWidth = 180; +const minSidebarWidth = 200; const Poll: React.VoidFunctionComponent = () => { const { t } = useTranslation("app"); - const { poll, targetTimeZone, setTargetTimeZone, options, userAlreadyVoted } = + const { poll, options, userAlreadyVoted, targetTimeZone, setTargetTimeZone } = usePoll(); const { participants } = useParticipants(); - const { timeZone } = poll; - const [ref, { width }] = useMeasure(); const [editingParticipantId, setEditingParticipantId] = React.useState(null); const actionColumnWidth = 140; const columnWidth = Math.min( - 100, + 130, Math.max( - 95, + 90, (width - minSidebarWidth - actionColumnWidth) / options.length, ), ); @@ -71,12 +66,7 @@ const Poll: React.VoidFunctionComponent = () => { const maxScrollPosition = columnWidth * options.length - columnWidth * numberOfVisibleColumns; - const numberOfInvisibleColumns = options.length - numberOfVisibleColumns; - - const [didUsePagination, setDidUsePagination] = React.useState(false); - - const [shouldShowNewParticipantForm, setShouldShowNewParticipantForm] = - React.useState(!userAlreadyVoted && !poll.closed); + const shouldShowNewParticipantForm = !userAlreadyVoted && !poll.closed; const pollWidth = sidebarWidth + options.length * columnWidth + actionColumnWidth; @@ -98,6 +88,7 @@ const Poll: React.VoidFunctionComponent = () => { ); }; + const participantListContainerRef = React.useRef(null); return ( { numberOfColumns: numberOfVisibleColumns, availableSpace, actionColumnWidth, + maxScrollPosition, }} >
-
-
-
- {timeZone ? ( -
-
- {t("timeZone")} -
- +
+ {poll.timeZone ? ( +
+
+
+ {t("timeZone")}
- ) : null} -
- +
-
+ ) : null} +
+
{ className="text-xs" rounded={true} onClick={() => { - setDidUsePagination(true); goToNextPage(); }} > - {didUsePagination ? ( - - ) : ( - `+${numberOfInvisibleColumns} more…` - )} + ) : null} ) : null}
- - {shouldShowNewParticipantForm && !poll.closed ? ( - { - await addParticipant.mutateAsync({ - name, - votes, - pollId: poll.pollId, - }); - setShouldShowNewParticipantForm(false); - }} - onCancel={() => { - setShouldShowNewParticipantForm(false); - }} - /> - ) : null} - -
-
- {participants.map((participant, i) => { - return ( - { - setEditingParticipantId(isEditing ? participant.id : null); - }} - /> - ); - })}
+ {participants.length > 0 ? ( +
+ {participants.map((participant, i) => { + return ( + { + setEditingParticipantId( + isEditing ? participant.id : null, + ); + }} + /> + ); + })} +
+ ) : null} + {shouldShowNewParticipantForm ? ( + { + const participant = await addParticipant.mutateAsync({ + name, + votes, + pollId: poll.id, + }); + setTimeout(() => { + participantListContainerRef.current + ?.querySelector(`[data-participantid=${participant.id}]`) + ?.scrollIntoView(); + }, 100); + }} + /> + ) : null}
diff --git a/src/components/poll/desktop-poll/participant-row-form.tsx b/src/components/poll/desktop-poll/participant-row-form.tsx index 8bdc237d7..9347fa664 100644 --- a/src/components/poll/desktop-poll/participant-row-form.tsx +++ b/src/components/poll/desktop-poll/participant-row-form.tsx @@ -33,6 +33,7 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< sidebarWidth, numberOfColumns, goToNextPage, + maxScrollPosition, setScrollPosition, } = usePollContext(); @@ -85,9 +86,8 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< render={({ field }) => (
0, + "input-error": errors.name && submitCount > 0, })} placeholder="Your name" {...field} @@ -160,16 +160,28 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< />
- - + {scrollPosition >= maxScrollPosition ? ( + + ) : null} + {scrollPosition < maxScrollPosition ? ( + + ) : null} + {onCancel ? : null}
); diff --git a/src/components/poll/desktop-poll/participant-row.tsx b/src/components/poll/desktop-poll/participant-row.tsx index 2c8c6c28a..eedd42a8d 100644 --- a/src/components/poll/desktop-poll/participant-row.tsx +++ b/src/components/poll/desktop-poll/participant-row.tsx @@ -32,6 +32,7 @@ export const ParticipantRowView: React.VoidFunctionComponent<{ columnWidth: number; sidebarWidth: number; isYou?: boolean; + participantId: string; }> = ({ name, editable, @@ -42,9 +43,14 @@ export const ParticipantRowView: React.VoidFunctionComponent<{ columnWidth, isYou, color, + participantId, }) => { return ( -
+
= ({ const isAnonymous = !participant.userId && !participant.guestId; - const canEdit = - !poll.closed && (poll.role === "admin" || isYou || isAnonymous); + const canEdit = !poll.closed && (poll.admin || isYou || isAnonymous); if (editMode) { return ( @@ -125,7 +130,7 @@ const ParticipantRow: React.VoidFunctionComponent = ({ onSubmit={async ({ name, votes }) => { await updateParticipant.mutateAsync({ participantId: participant.id, - pollId: poll.pollId, + pollId: poll.id, votes, name, }); @@ -144,6 +149,7 @@ const ParticipantRow: React.VoidFunctionComponent = ({ votes={options.map(({ optionId }) => { return getVote(participant.id, optionId); })} + participantId={participant.id} editable={canEdit} isYou={isYou} onEdit={() => { diff --git a/src/components/poll/desktop-poll/poll-context.ts b/src/components/poll/desktop-poll/poll-context.ts index c200c420f..ce8bddde8 100644 --- a/src/components/poll/desktop-poll/poll-context.ts +++ b/src/components/poll/desktop-poll/poll-context.ts @@ -5,6 +5,7 @@ export const PollContext = React.createContext<{ activeOptionId: string | null; setActiveOptionId: (optionId: string | null) => void; scrollPosition: number; + maxScrollPosition: number; setScrollPosition: (position: number) => void; columnWidth: number; sidebarWidth: number; @@ -17,6 +18,7 @@ export const PollContext = React.createContext<{ activeOptionId: null, setActiveOptionId: noop, scrollPosition: 0, + maxScrollPosition: 100, setScrollPosition: noop, columnWidth: 100, sidebarWidth: 200, diff --git a/src/components/poll/desktop-poll/poll-header.tsx b/src/components/poll/desktop-poll/poll-header.tsx index f7c8968ca..ab92be596 100644 --- a/src/components/poll/desktop-poll/poll-header.tsx +++ b/src/components/poll/desktop-poll/poll-header.tsx @@ -43,12 +43,12 @@ const PollHeader: React.VoidFunctionComponent = () => { onMouseOut={() => setActiveOptionId(null)} >
-
-
+
+
{option.dow}
-
{option.day}
-
+
{option.day}
+
{option.month}
diff --git a/src/components/poll/manage-poll.tsx b/src/components/poll/manage-poll.tsx index 6c7ba9c5c..d760a3221 100644 --- a/src/components/poll/manage-poll.tsx +++ b/src/components/poll/manage-poll.tsx @@ -27,7 +27,8 @@ const ManagePoll: React.VoidFunctionComponent<{ placement?: Placement; }> = ({ placement }) => { const { t } = useTranslation("app"); - const { poll, getParticipantsWhoVotedForOption, setDeleted } = usePoll(); + const { poll, getParticipantsWhoVotedForOption, setDeleted, urlId } = + usePoll(); const { exportToCsv } = useCsvExporter(); @@ -98,7 +99,7 @@ const ManagePoll: React.VoidFunctionComponent<{ const onOk = () => { updatePollMutation( { - urlId: poll.urlId, + urlId: urlId, timeZone: data.timeZone, optionsToDelete: optionsToDelete.map(({ id }) => id), optionsToAdd, @@ -165,7 +166,7 @@ const ManagePoll: React.VoidFunctionComponent<{ onSubmit={(data) => { //submit updatePollMutation( - { urlId: poll.urlId, ...data }, + { urlId, ...data }, { onSuccess: closePollDetailsModal }, ); }} @@ -195,17 +196,13 @@ const ManagePoll: React.VoidFunctionComponent<{ - updatePollMutation({ urlId: poll.urlId, closed: false }) - } + onClick={() => updatePollMutation({ urlId, closed: false })} /> ) : ( - updatePollMutation({ urlId: poll.urlId, closed: true }) - } + onClick={() => updatePollMutation({ urlId, closed: true })} /> )} ), footer: null, diff --git a/src/components/poll/mobile-poll.tsx b/src/components/poll/mobile-poll.tsx index 7a45562e2..9ff88db6f 100644 --- a/src/components/poll/mobile-poll.tsx +++ b/src/components/poll/mobile-poll.tsx @@ -10,7 +10,6 @@ import Check from "@/components/icons/check.svg"; import ChevronDown from "@/components/icons/chevron-down.svg"; import Pencil from "@/components/icons/pencil-alt.svg"; import PlusCircle from "@/components/icons/plus-circle.svg"; -import Save from "@/components/icons/save.svg"; import Trash from "@/components/icons/trash.svg"; import { usePoll } from "@/components/poll-context"; @@ -45,10 +44,11 @@ const MobilePoll: React.VoidFunctionComponent = () => { getParticipantById, optionIds, getVote, + userAlreadyVoted, } = pollContext; const { participants } = useParticipants(); - const { timeZone, role } = poll; + const { timeZone } = poll; const session = useSession(); @@ -60,38 +60,34 @@ const MobilePoll: React.VoidFunctionComponent = () => { }); const { reset, handleSubmit, control, formState } = form; - const [selectedParticipantId, setSelectedParticipantId] = - React.useState(); + const [selectedParticipantId, setSelectedParticipantId] = React.useState< + string | undefined + >(() => { + if (poll.admin) { + // don't select a particpant if admin + return; + } + const { user } = session; + if (user) { + const userParticipant = participants.find((participant) => + user.isGuest + ? participant.guestId === user.id + : participant.userId === user.id, + ); + return userParticipant?.id; + } + }); const selectedParticipant = selectedParticipantId ? getParticipantById(selectedParticipantId) : undefined; - const [isEditing, setIsEditing] = React.useState(false); + const [isEditing, setIsEditing] = React.useState( + !userAlreadyVoted && !poll.closed && !poll.admin, + ); - const [shouldShowSaveButton, setShouldShowSaveButton] = React.useState(false); const formRef = React.useRef(null); - React.useEffect(() => { - const setState = () => { - if (formRef.current) { - const rect = formRef.current.getBoundingClientRect(); - const saveButtonIsVisible = rect.bottom <= window.innerHeight; - - setShouldShowSaveButton( - !saveButtonIsVisible && - formRef.current.getBoundingClientRect().top < - window.innerHeight / 2, - ); - } - }; - setState(); - window.addEventListener("scroll", setState, true); - return () => { - window.removeEventListener("scroll", setState, true); - }; - }, []); - const { t } = useTranslation("app"); const updateParticipant = useUpdateParticipantMutation(); @@ -99,20 +95,6 @@ const MobilePoll: React.VoidFunctionComponent = () => { const addParticipant = useAddParticipantMutation(); const confirmDeleteParticipant = useDeleteParticipantModal(); - const submitContainerRef = React.useRef(null); - const scrollToSave = () => { - if (submitContainerRef.current) { - window.scrollTo({ - top: - document.documentElement.scrollTop + - submitContainerRef.current.getBoundingClientRect().bottom - - window.innerHeight + - 100, - behavior: "smooth", - }); - } - }; - return (
{ onSubmit={handleSubmit(async ({ name, votes }) => { if (selectedParticipant) { await updateParticipant.mutateAsync({ - pollId: poll.pollId, + pollId: poll.id, participantId: selectedParticipant.id, name, votes: normalizeVotes(optionIds, votes), @@ -129,7 +111,7 @@ const MobilePoll: React.VoidFunctionComponent = () => { setIsEditing(false); } else { const newParticipant = await addParticipant.mutateAsync({ - pollId: poll.pollId, + pollId: poll.id, name, votes: normalizeVotes(optionIds, votes), }); @@ -140,65 +122,84 @@ const MobilePoll: React.VoidFunctionComponent = () => { >
- { - setSelectedParticipantId(participantId); - }} - disabled={isEditing} - > -
- -
- {selectedParticipant ? ( -
- -
- ) : ( - t("participantCount", { count: participants.length }) - )} -
- -
- - - {t("participantCount", { count: participants.length })} - - {participants.map((participant) => ( - -
- -
+ {!isEditing ? ( + { + setSelectedParticipantId(participantId); + }} + disabled={isEditing} + > +
+ +
+ {selectedParticipant ? ( +
+ +
+ ) : ( + t("participantCount", { count: participants.length }) + )} +
+ +
+ + + {t("participantCount", { count: participants.length })} - ))} - + {participants.map((participant) => ( + +
+ +
+
+ ))} + +
+
+ ) : ( +
+ ( + + )} + />
- + )} {isEditing ? (
- ) : ( + ) : !userAlreadyVoted ? ( - )} + ) : null}
{timeZone ? ( { return `${option.month} ${option.year}`; }} /> - - {shouldShowSaveButton && isEditing ? ( - - - - ) : null} - {isEditing ? ( { transition: { duration: 0.2 }, }} > -
-
-
- ( - - )} - /> -
- -
+
+
) : null} diff --git a/src/components/poll/mobile-poll/poll-option.tsx b/src/components/poll/mobile-poll/poll-option.tsx index 834c48a8b..0cca4164b 100644 --- a/src/components/poll/mobile-poll/poll-option.tsx +++ b/src/components/poll/mobile-poll/poll-option.tsx @@ -29,7 +29,7 @@ const CollapsibleContainer: React.VoidFunctionComponent<{ className?: string; }> = ({ className, children, expanded }) => { return ( - + {expanded ? ( =
{noVotes ? (
- No one has vote for this option + No one has voted for this option
) : (
diff --git a/src/components/poll/mutations.ts b/src/components/poll/mutations.ts index 0d84d843f..c07fbad8a 100644 --- a/src/components/poll/mutations.ts +++ b/src/components/poll/mutations.ts @@ -26,7 +26,7 @@ export const useAddParticipantMutation = () => { queryClient.setQueryData( ["polls.participants.list", { pollId: participant.pollId }], (existingParticipants = []) => { - return [participant, ...existingParticipants]; + return [...existingParticipants, participant]; }, ); session.refresh(); @@ -79,12 +79,12 @@ export const useDeleteParticipantMutation = () => { }; export const useUpdatePollMutation = () => { - const { poll } = usePoll(); + const { urlId, admin } = usePoll(); const plausible = usePlausible(); const queryClient = trpc.useContext(); return trpc.useMutation(["polls.update"], { onSuccess: (data) => { - queryClient.setQueryData(["polls.get", { urlId: poll.urlId }], data); + queryClient.setQueryData(["polls.get", { urlId, admin }], data); plausible("Updated poll"); }, }); diff --git a/src/components/poll/notifications-toggle.tsx b/src/components/poll/notifications-toggle.tsx index 717a2afb6..d45f16aea 100644 --- a/src/components/poll/notifications-toggle.tsx +++ b/src/components/poll/notifications-toggle.tsx @@ -11,7 +11,7 @@ import Tooltip from "../tooltip"; import { useUpdatePollMutation } from "./mutations"; const NotificationsToggle: React.VoidFunctionComponent = () => { - const { poll } = usePoll(); + const { poll, urlId } = usePoll(); const { t } = useTranslation("app"); const [isUpdatingNotifications, setIsUpdatingNotifications] = React.useState(false); @@ -25,7 +25,7 @@ const NotificationsToggle: React.VoidFunctionComponent = () => { poll.verified ? ( poll.notifications ? (
-
+
Notifications are on
@@ -37,7 +37,7 @@ const NotificationsToggle: React.VoidFunctionComponent = () => { }} components={{ b: ( - + ), }} /> @@ -59,7 +59,7 @@ const NotificationsToggle: React.VoidFunctionComponent = () => { setIsUpdatingNotifications(true); updatePollMutation( { - urlId: poll.urlId, + urlId, notifications: !poll.notifications, }, { diff --git a/src/components/poll/poll-subheader.tsx b/src/components/poll/poll-subheader.tsx index 50c3c59ac..2586bff75 100644 --- a/src/components/poll/poll-subheader.tsx +++ b/src/components/poll/poll-subheader.tsx @@ -2,11 +2,8 @@ import { formatRelative } from "date-fns"; import { Trans, useTranslation } from "next-i18next"; import * as React from "react"; -import { trpc } from "../../utils/trpc"; import Badge from "../badge"; -import { Button } from "../button"; import { usePoll } from "../poll-context"; -import Popover from "../popover"; import { usePreferences } from "../preferences/use-preferences"; import Tooltip from "../tooltip"; @@ -14,12 +11,9 @@ const PollSubheader: React.VoidFunctionComponent = () => { const { poll } = usePoll(); const { t } = useTranslation("app"); const { locale } = usePreferences(); - const requestVerificationEmail = trpc.useMutation( - "polls.verification.request", - ); return ( -
+
{ name: poll.authorName, }} components={{ - b: , + b: , }} /> -   - - {poll.role === "admin" && !poll.demo ? ( - poll.verified ? ( - Verified - ) : ( - - Unverified - - } - > -
-
- - ), - }} - /> -
- {requestVerificationEmail.isSuccess ? ( -
- Verification email sent. -
- ) : ( - - )} -
-
- ) - ) : null} - {poll.legacy && poll.role === "admin" ? ( - - Legacy - - ) : null} - {poll.demo ? ( - }> - Demo - - ) : null} -
+ {poll.legacy && poll.admin ? ( + + + Legacy + + + ) : null} + {poll.demo ? ( + }> + + Demo + + + ) : null}
 •  - {formatRelative(new Date(poll.createdAt), new Date(), { + {formatRelative(poll.createdAt, new Date(), { locale, })} diff --git a/src/components/poll/unverified-poll-notice.tsx b/src/components/poll/unverified-poll-notice.tsx new file mode 100644 index 000000000..1341168a7 --- /dev/null +++ b/src/components/poll/unverified-poll-notice.tsx @@ -0,0 +1,46 @@ +import { Trans, useTranslation } from "next-i18next"; + +import { trpc } from "../../utils/trpc"; +import { Button } from "../button"; +import { usePoll } from "../poll-context"; + +export const UnverifiedPollNotice = () => { + const { t } = useTranslation("app"); + const { poll } = usePoll(); + const requestVerificationEmail = trpc.useMutation( + "polls.verification.request", + ); + + return ( +
+
+
+ + ), + }} + /> +
+ +
+
+ ); +}; diff --git a/src/components/poll/use-delete-participant-modal.ts b/src/components/poll/use-delete-participant-modal.ts index e06918a03..0a0a56fb5 100644 --- a/src/components/poll/use-delete-participant-modal.ts +++ b/src/components/poll/use-delete-participant-modal.ts @@ -6,9 +6,7 @@ export const useDeleteParticipantModal = () => { const { render } = useModalContext(); const deleteParticipant = useDeleteParticipantMutation(); - const { - poll: { pollId }, - } = usePoll(); + const { poll } = usePoll(); return (participantId: string) => { return render({ @@ -21,7 +19,7 @@ export const useDeleteParticipantModal = () => { okText: "Delete", onOk: () => { deleteParticipant.mutate({ - pollId, + pollId: poll.id, participantId, }); }, diff --git a/src/components/profile.tsx b/src/components/profile.tsx index f12a2af34..548a97093 100644 --- a/src/components/profile.tsx +++ b/src/components/profile.tsx @@ -35,8 +35,8 @@ export const Profile: React.VoidFunctionComponent = () => { return (
-
- +
+
{
- - - + + +
{poll.title}
diff --git a/src/components/sharing.tsx b/src/components/sharing.tsx index e349daf10..01fb50d47 100644 --- a/src/components/sharing.tsx +++ b/src/components/sharing.tsx @@ -1,102 +1,79 @@ -import { Link, Role } from "@prisma/client"; import clsx from "clsx"; -import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import { Trans, useTranslation } from "next-i18next"; import * as React from "react"; import toast from "react-hot-toast"; import { useCopyToClipboard } from "react-use"; import { Button } from "./button"; +import { usePoll } from "./poll-context"; export interface SharingProps { - links: Link[]; + onHide: () => void; + className?: string; } -const useRoleData = (): Record< - Role, - { path: string; label: string; description: string } -> => { +const Sharing: React.VoidFunctionComponent = ({ + onHide, + className, +}) => { + const { poll } = usePoll(); const { t } = useTranslation("app"); - return { - admin: { - path: "admin", - label: t("admin"), - description: t("adminDescription"), - }, - participant: { - path: "p", - label: t("participant"), - description: t("participantDescription"), - }, - }; -}; - -const Sharing: React.VoidFunctionComponent = ({ links }) => { const [state, copyToClipboard] = useCopyToClipboard(); - const plausible = usePlausible(); - React.useEffect(() => { if (state.error) { toast.error(`Unable to copy value: ${state.error.message}`); } }, [state]); - const [role, setRole] = React.useState("participant"); - const link = links.find((link) => link.role === role); - if (!link) { - throw new Error(`Missing link for role: ${role}`); - } - const roleData = useRoleData(); - const { path } = roleData[link.role]; - const pollUrl = `${window.location.origin}/${path}/${link.urlId}`; + const participantUrl = `${window.location.origin}/p/${poll.participantUrlId}`; const [didCopy, setDidCopy] = React.useState(false); return ( -
-
+
+
+
+ Share via link +
-
-
- +
+ }} + /> +
+
+
-
{roleData[link.role].description}
); }; diff --git a/src/components/standard-layout.tsx b/src/components/standard-layout.tsx index 315049f17..54d592378 100644 --- a/src/components/standard-layout.tsx +++ b/src/components/standard-layout.tsx @@ -30,7 +30,7 @@ const HomeLink = () => { return ( - + ); @@ -41,7 +41,10 @@ const MobileNavigation: React.VoidFunctionComponent<{ }> = ({ openLoginModal }) => { const { user } = useSession(); return ( -
+
@@ -72,7 +75,7 @@ const MobileNavigation: React.VoidFunctionComponent<{ className="group inline-flex w-full items-center space-x-2 rounded-lg px-2 py-1 text-left transition-colors hover:bg-slate-500/10 active:bg-slate-500/20" >
- +
{user.shortName} @@ -89,7 +92,7 @@ const MobileNavigation: React.VoidFunctionComponent<{ type="button" className="group flex items-center whitespace-nowrap rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300" > - + Preferences } @@ -103,7 +106,7 @@ const MobileNavigation: React.VoidFunctionComponent<{ type="button" className="group flex items-center rounded-md px-2 py-1 font-medium text-slate-600 transition-colors hover:bg-gray-200 hover:text-slate-600 hover:no-underline active:bg-gray-300" > - + Menu } @@ -160,7 +163,7 @@ const UserDropdown: React.VoidFunctionComponent< content: (
-
+
@@ -251,7 +254,7 @@ const StandardLayout: React.VoidFunctionComponent<{
- + New Poll @@ -261,14 +264,14 @@ const StandardLayout: React.VoidFunctionComponent<{ className="group mb-1 flex items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20" rel="noreferrer" > - + Support - + Preferences @@ -281,7 +284,7 @@ const StandardLayout: React.VoidFunctionComponent<{ onClick={openLoginModal} className="group flex w-full items-center space-x-3 whitespace-nowrap rounded-md px-3 py-1 font-medium text-slate-600 transition-colors hover:bg-slate-500/10 hover:text-slate-600 hover:no-underline active:bg-slate-500/20" > - + Login )} @@ -301,7 +304,7 @@ const StandardLayout: React.VoidFunctionComponent<{ >
- +
@@ -327,7 +330,7 @@ const StandardLayout: React.VoidFunctionComponent<{
- + @@ -337,30 +340,30 @@ const StandardLayout: React.VoidFunctionComponent<{ Support - + Discussions - + Blog
- + - + @@ -368,7 +371,7 @@ const StandardLayout: React.VoidFunctionComponent<{
- + Donate diff --git a/src/components/text-input.tsx b/src/components/text-input.tsx index a204fd2d2..fe0f149b1 100644 --- a/src/components/text-input.tsx +++ b/src/components/text-input.tsx @@ -7,18 +7,28 @@ export interface TextInputProps HTMLInputElement > { error?: boolean; + proportions?: "lg" | "md"; } export const TextInput = React.forwardRef( - function TextInput({ className, error, ...forwardProps }, ref) { + function TextInput( + { className, error, proportions: size = "md", ...forwardProps }, + ref, + ) { return ( ); diff --git a/src/components/time-zone-picker/time-zone-picker.tsx b/src/components/time-zone-picker/time-zone-picker.tsx index 41491a477..78cce698e 100644 --- a/src/components/time-zone-picker/time-zone-picker.tsx +++ b/src/components/time-zone-picker/time-zone-picker.tsx @@ -1,3 +1,10 @@ +import { + flip, + FloatingPortal, + offset, + size, + useFloating, +} from "@floating-ui/react-dom-interactions"; import { Combobox } from "@headlessui/react"; import clsx from "clsx"; import React from "react"; @@ -119,6 +126,23 @@ const TimeZonePicker: React.VoidFunctionComponent<{ }> = ({ value, onChange, onBlur, className, style, disabled }) => { const { options, findFuzzyTz } = useTimeZones(); + const { reference, floating, x, y, strategy, refs } = useFloating({ + strategy: "fixed", + middleware: [ + offset(5), + flip(), + size({ + apply: ({ reference }) => { + if (refs.floating.current) { + Object.assign(refs.floating.current.style, { + width: `${reference.width}px`, + }); + } + }, + }), + ], + }); + const timeZoneOptions = React.useMemo( () => [ { @@ -164,7 +188,11 @@ const TimeZonePicker: React.VoidFunctionComponent<{ }} disabled={disabled} > -
+
{/* Remove generic params once Combobox.Input can infer the types */} className="input w-full pr-8" @@ -182,17 +210,27 @@ const TimeZonePicker: React.VoidFunctionComponent<{ - - {filteredTimeZones.map((timeZone) => ( - - {timeZone.label} - - ))} - + + + {filteredTimeZones.map((timeZone) => ( + + {timeZone.label} + + ))} + +
); diff --git a/src/pages/api/house-keeping.ts b/src/pages/api/house-keeping.ts index c4cc58abb..00799c4a8 100644 --- a/src/pages/api/house-keeping.ts +++ b/src/pages/api/house-keeping.ts @@ -55,23 +55,15 @@ export default async function handler( ], }, select: { - urlId: true, + id: true, }, orderBy: { createdAt: "asc", // oldest first }, }) - ).map(({ urlId }) => urlId); + ).map(({ id }) => id); if (pollIdsToDelete.length !== 0) { - // Delete links - await prisma.link.deleteMany({ - where: { - pollId: { - in: pollIdsToDelete, - }, - }, - }); // Delete comments await prisma.comment.deleteMany({ where: { @@ -107,9 +99,13 @@ export default async function handler( }, }); + await prisma.$executeRaw`DELETE FROM options WHERE poll_id IN (${Prisma.join( + pollIdsToDelete, + )})`; + // Delete polls // Using execute raw to bypass soft delete middelware - await prisma.$executeRaw`DELETE FROM polls WHERE url_id IN (${Prisma.join( + await prisma.$executeRaw`DELETE FROM polls WHERE id IN (${Prisma.join( pollIdsToDelete, )})`; } diff --git a/src/pages/api/legacy/[urlId].ts b/src/pages/api/legacy/[urlId].ts deleted file mode 100644 index e18254f7e..000000000 --- a/src/pages/api/legacy/[urlId].ts +++ /dev/null @@ -1,166 +0,0 @@ -import { VoteType } from "@prisma/client"; -import { NextApiRequest, NextApiResponse } from "next"; - -import { getQueryParam } from "@/utils/api-utils"; -import { LegacyPoll } from "@/utils/legacy-utils"; -import { getMongoClient } from "@/utils/mongodb-client"; -import { nanoid } from "@/utils/nanoid"; -import { GetPollApiResponse } from "@/utils/trpc/types"; -import { prisma } from "~/prisma/db"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const urlId = getQueryParam(req, "urlId"); - - const client = await getMongoClient(); - if (!client) { - // This environment is not configured to retrieve legacy polls - // from mongodb - return res.status(404).end(); - } - - const db = client.db("rallly-db"); - const collection = db.collection("events"); - - const legacyPoll = await collection.findOne({ _id: urlId }); - - if ( - !legacyPoll || - !legacyPoll.dates || - legacyPoll.dates.length === 0 || - legacyPoll.isDeleted === true - ) { - // return 404 if poll is missing or malformed or deleted - return res.status(404).end(); - } - - const newOptions: Array<{ id: string; value: string }> = []; - - for (let i = 0; i < legacyPoll.dates.length; i++) { - const date = legacyPoll.dates[i].toISOString(); - newOptions.push({ - id: await nanoid(), - value: date, - }); - } - - const newParticipants = legacyPoll.participants?.map((legacyParticipant) => ({ - name: legacyParticipant.name, - id: legacyParticipant._id.toString(), - })); - - const votes: Array<{ - optionId: string; - participantId: string; - type: VoteType; - }> = []; - - newParticipants?.forEach((p, i) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const legacyVotes = legacyPoll.participants![i].votes; - legacyVotes?.forEach((v, j) => { - votes.push({ - optionId: newOptions[j].id, - participantId: p.id, - type: v ? "yes" : "no", - }); - }); - }); - - const poll = await prisma.poll.create({ - data: { - legacy: true, - closed: legacyPoll.isClosed, - urlId: legacyPoll._id, - title: legacyPoll.title, - location: legacyPoll.location, - description: legacyPoll.description, - demo: legacyPoll.isExample, - createdAt: new Date(legacyPoll.created), - type: "date", - authorName: legacyPoll.creator.name, - verified: legacyPoll.creator.isVerified, - user: { - connectOrCreate: { - where: { - email: legacyPoll.creator.email, - }, - create: { - id: await nanoid(), - email: legacyPoll.creator.email, - name: legacyPoll.creator.name, - }, - }, - }, - notifications: legacyPoll.creator.allowNotifications, - options: { - createMany: { - data: newOptions, - }, - }, - participants: { - createMany: { - data: newParticipants ?? [], - }, - }, - votes: { - createMany: { - data: votes, - }, - }, - comments: { - createMany: { - data: - legacyPoll.comments?.map((legacyComment) => ({ - content: legacyComment.content, - createdAt: new Date(legacyComment.created), - authorName: legacyComment.author.name, - })) ?? [], - }, - }, - links: { - createMany: { - data: [ - { - role: "admin", - urlId: legacyPoll._id, - }, - { - role: "participant", - urlId: await nanoid(), - }, - ], - }, - }, - }, - include: { - options: { - orderBy: { - value: "asc", - }, - }, - participants: { - include: { - votes: true, - }, - orderBy: [ - { - createdAt: "desc", - }, - { name: "desc" }, - ], - }, - user: true, - links: true, - }, - }); - - return res.json({ - ...poll, - role: "admin", - urlId: poll.urlId, - pollId: poll.urlId, - }); -} diff --git a/src/pages/poll.tsx b/src/pages/poll.tsx index f706c8612..25ca4fe37 100644 --- a/src/pages/poll.tsx +++ b/src/pages/poll.tsx @@ -1,10 +1,8 @@ -import axios from "axios"; import { GetServerSideProps, NextPage } from "next"; import dynamic from "next/dynamic"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import { usePlausible } from "next-plausible"; import React from "react"; import FullPageLoader from "@/components/full-page-loader"; @@ -14,42 +12,30 @@ import { SessionProps, withSession } from "@/components/session"; import { ParticipantsProvider } from "../components/participants-provider"; import { withSessionSsr } from "../utils/auth"; import { trpc } from "../utils/trpc"; -import { GetPollApiResponse } from "../utils/trpc/types"; import Custom404 from "./404"; const PollPage = dynamic(() => import("@/components/poll"), { ssr: false }); const PollPageLoader: NextPage = () => { - const { query } = useRouter(); + const { query, asPath } = useRouter(); const { t } = useTranslation("app"); const urlId = query.urlId as string; - const plausible = usePlausible(); const [notFound, setNotFound] = React.useState(false); - const [legacyPoll, setLegacyPoll] = React.useState(); - const pollQuery = trpc.useQuery(["polls.get", { urlId }], { + const admin = /^\/admin/.test(asPath); + const pollQuery = trpc.useQuery(["polls.get", { urlId, admin }], { onError: () => { - if (process.env.NEXT_PUBLIC_LEGACY_POLLS === "1") { - axios - .get(`/api/legacy/${urlId}`) - .then(({ data }) => { - plausible("Converted legacy event"); - setLegacyPoll(data); - }) - .catch(() => setNotFound(true)); - } else { - setNotFound(true); - } + setNotFound(true); }, retry: false, }); - const poll = pollQuery.data ?? legacyPoll; + const poll = pollQuery.data; if (poll) { return ( - - + + diff --git a/src/server/routers/polls.ts b/src/server/routers/polls.ts index fdae32478..efefa6f9c 100644 --- a/src/server/routers/polls.ts +++ b/src/server/routers/polls.ts @@ -7,12 +7,6 @@ import { absoluteUrl } from "../../utils/absolute-url"; import { sendEmailTemplate } from "../../utils/api-utils"; import { createToken } from "../../utils/auth"; import { nanoid } from "../../utils/nanoid"; -import { - createPollResponse, - getDefaultPollInclude, - getLink, - getPollFromLink, -} from "../../utils/queries"; import { GetPollApiResponse } from "../../utils/trpc/types"; import { createRouter } from "../createRouter"; import { comments } from "./polls/comments"; @@ -20,6 +14,66 @@ import { demo } from "./polls/demo"; import { participants } from "./polls/participants"; import { verification } from "./polls/verification"; +const defaultSelectFields: { + id: true; + timeZone: true; + title: true; + authorName: true; + location: true; + description: true; + createdAt: true; + participantUrlId: true; + adminUrlId: true; + verified: true; + closed: true; + legacy: true; + demo: true; + notifications: true; + options: { + orderBy: { + value: "asc"; + }; + }; + user: true; +} = { + id: true, + timeZone: true, + title: true, + authorName: true, + location: true, + description: true, + createdAt: true, + participantUrlId: true, + adminUrlId: true, + verified: true, + closed: true, + legacy: true, + notifications: true, + demo: true, + options: { + orderBy: { + value: "asc", + }, + }, + user: true, +}; + +const getPollIdFromAdminUrlId = async (urlId: string) => { + const res = await prisma.poll.findUnique({ + select: { + id: true, + }, + where: { adminUrlId: urlId }, + }); + + if (!res) { + throw new TRPCError({ + code: "NOT_FOUND", + }); + } + return res.id; +}; + export const polls = createRouter() .merge("demo.", demo) .merge("participants.", participants) @@ -39,12 +93,12 @@ export const polls = createRouter() options: z.string().array(), demo: z.boolean().optional(), }), - resolve: async ({ ctx, input }) => { + resolve: async ({ ctx, input }): Promise<{ urlId: string }> => { const adminUrlId = await nanoid(); const poll = await prisma.poll.create({ data: { - urlId: await nanoid(), + id: await nanoid(), title: input.title, type: input.type, timeZone: input.timeZone, @@ -55,6 +109,8 @@ export const polls = createRouter() verified: ctx.session.user?.isGuest === false && ctx.session.user.email === input.user.email, + adminUrlId, + participantUrlId: await nanoid(), user: { connectOrCreate: { where: { @@ -73,20 +129,6 @@ export const polls = createRouter() })), }, }, - links: { - createMany: { - data: [ - { - urlId: adminUrlId, - role: "admin", - }, - { - urlId: await nanoid(), - role: "participant", - }, - ], - }, - }, }, }); @@ -109,7 +151,7 @@ export const polls = createRouter() }); } else { const verificationCode = await createToken({ - pollId: poll.urlId, + pollId: poll.id, }); const verifyEmailUrl = `${pollUrl}?code=${verificationCode}`; @@ -131,16 +173,38 @@ export const polls = createRouter() console.error(e); } - return { urlId: adminUrlId, authorName: poll.authorName }; + return { urlId: adminUrlId }; }, }) .query("get", { input: z.object({ urlId: z.string(), + admin: z.boolean(), }), - resolve: async ({ input }): Promise => { - const link = await getLink(input.urlId); - return await getPollFromLink(link); + resolve: async ({ input, ctx }): Promise => { + const poll = await prisma.poll.findFirst({ + select: defaultSelectFields, + where: input.admin + ? { + adminUrlId: input.urlId, + } + : { + participantUrlId: input.urlId, + }, + }); + + if (!poll) { + throw new TRPCError({ + code: "NOT_FOUND", + }); + } + + // We want to keep the adminUrlId in if the user is view + if (!input.admin && ctx.session.user?.id !== poll.user.id) { + return { ...poll, admin: input.admin, adminUrlId: "" }; + } + + return { ...poll, admin: input.admin }; }, }) .mutation("update", { @@ -156,13 +220,7 @@ export const polls = createRouter() closed: z.boolean().optional(), }), resolve: async ({ input }): Promise => { - const link = await getLink(input.urlId); - - if (link.role !== "admin") { - throw new Error("Use admin link to update poll"); - } - - const { pollId } = link; + const pollId = await getPollIdFromAdminUrlId(input.urlId); if (input.optionsToDelete && input.optionsToDelete.length > 0) { await prisma.option.deleteMany({ @@ -185,8 +243,9 @@ export const polls = createRouter() } const poll = await prisma.poll.update({ + select: defaultSelectFields, where: { - urlId: pollId, + id: pollId, }, data: { title: input.title, @@ -196,10 +255,9 @@ export const polls = createRouter() notifications: input.notifications, closed: input.closed, }, - include: getDefaultPollInclude(link.role === "admin"), }); - return createPollResponse(poll, link); + return { ...poll, admin: true }; }, }) .mutation("delete", { @@ -207,15 +265,8 @@ export const polls = createRouter() urlId: z.string(), }), resolve: async ({ input: { urlId } }) => { - const link = await getLink(urlId); - if (link.role !== "admin") { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Tried to delete poll using participant url", - }); - } - - await prisma.poll.delete({ where: { urlId: link.pollId } }); + const pollId = await getPollIdFromAdminUrlId(urlId); + await prisma.poll.delete({ where: { id: pollId } }); }, }) .mutation("touch", { @@ -225,7 +276,7 @@ export const polls = createRouter() resolve: async ({ input: { pollId } }) => { await prisma.poll.update({ where: { - urlId: pollId, + id: pollId, }, data: { touchedAt: new Date(), diff --git a/src/server/routers/polls/demo.ts b/src/server/routers/polls/demo.ts index 0d2fd30e3..8eebccddf 100644 --- a/src/server/routers/polls/demo.ts +++ b/src/server/routers/polls/demo.ts @@ -3,7 +3,6 @@ import addMinutes from "date-fns/addMinutes"; import { prisma } from "~/prisma/db"; -import { absoluteUrl } from "../../../utils/absolute-url"; import { nanoid } from "../../../utils/nanoid"; import { createRouter } from "../../createRouter"; @@ -72,18 +71,18 @@ export const demo = createRouter().mutation("create", { }); } - const homePageUrl = absoluteUrl(); - await prisma.poll.create({ data: { - urlId: await nanoid(), - title: "Lunch Meeting Demo", + id: await nanoid(), + title: "Lunch Meeting", type: "date", location: "Starbucks, 901 New York Avenue", - description: `This poll has been automatically generated just for you! Feel free to try out all the different features and when you're ready, you can go to ${homePageUrl}/new to make a new poll.`, + description: `Hey everyone, please choose the dates when you are available to meet for our monthly get together. Looking forward to see you all!`, authorName: "Johnny", verified: true, demo: true, + adminUrlId, + participantUrlId: await nanoid(), user: { connectOrCreate: { where: { @@ -97,20 +96,6 @@ export const demo = createRouter().mutation("create", { data: options, }, }, - links: { - createMany: { - data: [ - { - role: "admin", - urlId: adminUrlId, - }, - { - role: "participant", - urlId: await nanoid(), - }, - ], - }, - }, participants: { createMany: { data: participants, diff --git a/src/server/routers/polls/participants.ts b/src/server/routers/polls/participants.ts index 094bc7675..7a1754f1a 100644 --- a/src/server/routers/polls/participants.ts +++ b/src/server/routers/polls/participants.ts @@ -21,7 +21,7 @@ export const participants = createRouter() }, orderBy: [ { - createdAt: "desc", + createdAt: "asc", }, { name: "desc" }, ], @@ -48,7 +48,7 @@ export const participants = createRouter() .mutation("add", { input: z.object({ pollId: z.string(), - name: z.string(), + name: z.string().nonempty("Participant name is required"), votes: z .object({ optionId: z.string(), diff --git a/src/server/routers/polls/verification.ts b/src/server/routers/polls/verification.ts index f4a1e42a6..31de37200 100644 --- a/src/server/routers/polls/verification.ts +++ b/src/server/routers/polls/verification.ts @@ -32,7 +32,7 @@ export const verification = createRouter() const poll = await prisma.poll.update({ where: { - urlId: pollId, + id: pollId, }, data: { verified: true, @@ -63,11 +63,10 @@ export const verification = createRouter() resolve: async ({ input: { pollId, adminUrlId } }) => { const poll = await prisma.poll.findUnique({ where: { - urlId: pollId, + id: pollId, }, include: { user: true, - links: true, }, }); diff --git a/src/server/routers/user.ts b/src/server/routers/user.ts index 3012d7f21..c0fbd6da3 100644 --- a/src/server/routers/user.ts +++ b/src/server/routers/user.ts @@ -34,13 +34,9 @@ export const user = createRouter() closed: true, verified: true, createdAt: true, - links: { - where: { - role: "admin", - }, - }, + adminUrlId: true, }, - take: 5, + take: 10, orderBy: { createdAt: "desc", }, diff --git a/src/utils/api-utils.ts b/src/utils/api-utils.ts index 2741e0a4a..003878295 100644 --- a/src/utils/api-utils.ts +++ b/src/utils/api-utils.ts @@ -1,7 +1,6 @@ -import { Link } from "@prisma/client"; import * as Eta from "eta"; import { readFileSync } from "fs"; -import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; +import { NextApiRequest } from "next"; import path from "path"; import { prisma } from "~/prisma/db"; @@ -14,38 +13,6 @@ export const getQueryParam = (req: NextApiRequest, queryKey: string) => { return typeof value === "string" ? value : value[0]; }; -type ApiMiddleware> = ( - ctx: { - req: NextApiRequest; - res: NextApiResponse; - } & P, -) => Promise; - -/** - * Gets the Link from `req.query.urlId` and passes it to handler - * @param handler - * @returns - */ -export const withLink = ( - handler: ApiMiddleware, -): NextApiHandler => { - return async (req, res) => { - const urlId = getQueryParam(req, "urlId"); - const link = await prisma.link.findUnique({ where: { urlId } }); - - if (!link) { - res.status(404).json({ - status: 404, - message: `Could not find link with urlId: ${urlId}`, - }); - return; - } - - await handler({ req, res, link }); - return; - }; -}; - type NotificationAction = | { type: "newParticipant"; @@ -62,8 +29,8 @@ export const sendNotification = async ( ): Promise => { try { const poll = await prisma.poll.findUnique({ - where: { urlId: pollId }, - include: { user: true, links: true }, + where: { id: pollId }, + include: { user: true }, }); /** * poll needs to: @@ -79,12 +46,8 @@ export const sendNotification = async ( !poll.demo && poll.notifications ) { - const adminLink = getAdminLink(poll.links); - if (!adminLink) { - throw new Error(`Missing admin link for poll: ${pollId}`); - } const homePageUrl = absoluteUrl(); - const pollUrl = `${homePageUrl}/admin/${adminLink.urlId}`; + const pollUrl = `${homePageUrl}/admin/${poll.adminUrlId}`; const unsubscribeUrl = `${pollUrl}?unsubscribe=true`; switch (action.type) { @@ -127,9 +90,6 @@ export const sendNotification = async ( } }; -export const getAdminLink = (links: Link[]) => - links.find((link) => link.role === "admin"); - interface SendEmailTemplateParams { templateName: string; to: string; diff --git a/src/utils/legacy-utils.ts b/src/utils/legacy-utils.ts deleted file mode 100644 index 1d42c9b0e..000000000 --- a/src/utils/legacy-utils.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { ObjectId } from "mongodb"; - -import { prisma } from "~/prisma/db"; - -import { getMongoClient } from "./mongodb-client"; - -export interface LegacyPoll { - __private: { - verificationCode: string; - }; - _id: string; - title: string; - location: string; - isExample: boolean; - isDeleted: boolean; - isClosed: boolean; - emails: string[]; - description: string; - dates?: Date[]; - creator: { - name: string; - email: string; - isVerified: boolean; - allowNotifications: boolean; - }; - created: Date; - comments?: Array<{ - _id: ObjectId; - author: { - name: string; - }; - content: string; - created: Date; - }>; - participants?: Array<{ - _id: ObjectId; - name: string; - votes?: boolean[]; - }>; -} - -export const resetDates = async (legacyPollId: string) => { - const client = await getMongoClient(); - if (!client) { - return; - } - const db = client.db("rallly-db"); - const collection = db.collection("events"); - - const legacyPoll = await collection.findOne({ - _id: legacyPollId, - }); - - if (!legacyPoll) { - return; - } - - const existingOptions = await prisma.option.findMany({ - where: { pollId: legacyPoll._id }, - orderBy: { - value: "asc", - }, - }); - - if (!existingOptions) { - return; - } - - const promises = []; - for (let i = 0; i < existingOptions.length; i++) { - const existingOption = existingOptions[i]; - if (existingOption.value.indexOf("T") === -1) { - const legacyOption = legacyPoll.dates?.find( - (date) => date.toISOString().substring(0, 10) === existingOption.value, - ); - if (legacyOption) { - promises.push( - prisma.option.update({ - where: { id: existingOption.id }, - data: { - value: legacyOption.toISOString(), - }, - }), - ); - } - } - } - await prisma.$transaction(promises); - - const poll = await prisma.poll.findUnique({ - where: { - urlId: legacyPoll._id, - }, - include: { - options: { - orderBy: { - value: "asc", - }, - }, - participants: { - include: { - votes: true, - }, - orderBy: [ - { - createdAt: "desc", - }, - { name: "desc" }, - ], - }, - user: true, - links: true, - }, - }); - - return poll; -}; diff --git a/src/utils/mongodb-client.ts b/src/utils/mongodb-client.ts deleted file mode 100644 index 2eb73f07d..000000000 --- a/src/utils/mongodb-client.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Import the dependency. -import { MongoClient } from "mongodb"; - -declare global { - // allow global `var` declarations - // eslint-disable-next-line no-var - var _mongoClientPromise: Promise; -} - -const uri = process.env.LEGACY_MONGODB_URI; - -let client; -let clientPromise; - -export const getMongoClient = async () => { - if (!uri) { - return; - } - - if (global._mongoClientPromise) { - return global._mongoClientPromise; - } - - client = new MongoClient(uri); - clientPromise = client.connect(); - - if (process.env.NODE_ENV === "development") { - global._mongoClientPromise = clientPromise; - } - - return clientPromise; -}; diff --git a/src/utils/queries.ts b/src/utils/queries.ts deleted file mode 100644 index 1e1683694..000000000 --- a/src/utils/queries.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Link, Option, Poll, User } from "@prisma/client"; -import { TRPCError } from "@trpc/server"; - -import { prisma } from "~/prisma/db"; - -import { GetPollApiResponse } from "./trpc/types"; - -export const getDefaultPollInclude = ( - includeLinks: V, -): { - options: { - orderBy: { - value: "asc"; - }; - }; - user: true; - links: V; -} => { - return { - options: { - orderBy: { - value: "asc", - }, - }, - user: true, - links: includeLinks, - }; -}; - -export const getLink = async (urlId: string) => { - const link = await prisma.link.findUnique({ - where: { - urlId, - }, - }); - - if (!link) { - throw new TRPCError({ - code: "NOT_FOUND", - message: `Link not found with id: ${urlId}`, - }); - } - - return link; -}; - -export const getPollFromLink = async ( - link: Link, -): Promise => { - const poll = await prisma.poll.findUnique({ - where: { - urlId: link.pollId, - }, - include: getDefaultPollInclude(link.role === "admin"), - }); - - if (!poll) { - throw new TRPCError({ - code: "NOT_FOUND", - message: `Poll not found with id: ${link.pollId}`, - }); - } - - return createPollResponse(poll, link); -}; - -export const createPollResponse = ( - poll: Poll & { - options: Option[]; - user: User; - links: Link[]; - }, - link: Link, -): GetPollApiResponse => { - return { - ...poll, - role: link.role, - urlId: link.urlId, - pollId: poll.urlId, - }; -}; diff --git a/src/utils/trpc/types.ts b/src/utils/trpc/types.ts index e634a678c..18ba130bb 100644 --- a/src/utils/trpc/types.ts +++ b/src/utils/trpc/types.ts @@ -1,9 +1,21 @@ -import { Link, Option, Poll, Role, User } from "@prisma/client"; +import { Option, User } from "@prisma/client"; -export interface GetPollApiResponse extends Poll { +export type GetPollApiResponse = { + id: string; + title: string; + authorName: string; + location: string | null; + description: string | null; options: Option[]; user: User; - role: Role; - links: Array; - pollId: string; -} + timeZone: string | null; + adminUrlId: string; + participantUrlId: string; + verified: boolean; + closed: boolean; + admin: boolean; + legacy: boolean; + demo: boolean; + notifications: boolean; + createdAt: Date; +}; diff --git a/style.css b/style.css index 3db518d96..b445b19f1 100644 --- a/style.css +++ b/style.css @@ -7,7 +7,7 @@ body, #__next { height: 100%; - @apply bg-slate-50; + @apply overflow-x-hidden bg-slate-50; } body { @apply bg-slate-50 text-base text-slate-600; @@ -32,13 +32,13 @@ @apply outline-none; } a { - @apply text-primary-500 hover:text-primary-400 focus-visible:ring-primary-500 rounded-sm font-medium outline-none hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1; + @apply rounded-sm font-medium text-primary-500 outline-none hover:text-primary-400 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1; } label { @apply mb-1 block text-sm text-slate-800; } button { - @apply focus-visible:ring-primary-500 cursor-default outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1; + @apply cursor-default outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1; } #floating-ui-root { @@ -51,7 +51,7 @@ @apply mb-4; } .input { - @apply focus:border-primary-500 focus:ring-primary-500 appearance-none rounded-md border border-gray-200 px-2 py-1 text-slate-700 shadow-sm placeholder:text-slate-400 focus:ring-1; + @apply appearance-none rounded-md border border-gray-200 px-2 py-1 text-slate-700 shadow-sm placeholder:text-slate-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500; } input.input { @apply h-9; @@ -63,17 +63,17 @@ @apply border-rose-500 ring-1 ring-rose-400 focus:border-rose-400 focus:ring-rose-500; } .checkbox { - @apply text-primary-500 focus:ring-primary-500 h-4 w-4 rounded border-slate-300 shadow-sm; + @apply h-4 w-4 rounded border-slate-300 text-primary-500 shadow-sm focus:ring-primary-500; } .btn { - @apply inline-flex h-9 cursor-default select-none items-center justify-center whitespace-nowrap rounded-md border px-3 font-medium shadow-sm transition-all sm:active:scale-95; + @apply inline-flex h-9 cursor-default select-none items-center justify-center whitespace-nowrap rounded-md border px-3 font-medium shadow-sm transition-all; } a.btn { @apply cursor-pointer hover:no-underline; } .btn-default { - @apply btn hover:bg-primary-50/10 bg-white text-slate-700 active:bg-slate-100; + @apply btn bg-white text-slate-700 hover:bg-slate-100/10 active:bg-slate-500/10; } a.btn-default { @@ -83,7 +83,7 @@ @apply btn border-rose-600 bg-rose-500 text-white hover:bg-rose-600 focus-visible:ring-rose-500; } .btn-link { - @apply text-primary-500 inline-flex items-center underline; + @apply inline-flex items-center text-primary-500 underline; } .btn.btn-disabled { text-shadow: none; @@ -91,7 +91,7 @@ } .btn-primary { text-shadow: rgb(0 0 0 / 20%) 0px 1px 1px; - @apply btn border-primary-600 bg-primary-500 focus-visible:ring-primary-500 active:bg-primary-600 text-white hover:bg-opacity-90; + @apply btn border-primary-600 bg-primary-500 text-white hover:bg-opacity-90 focus-visible:ring-primary-500 active:bg-primary-600; } a.btn-primary { @@ -127,13 +127,13 @@ } .card { - @apply border-t border-b bg-white p-6 shadow-sm sm:rounded-lg sm:border-l sm:border-r; + @apply border-y bg-white p-4 shadow-sm md:rounded-lg md:border; } } @layer components { .heading { - @apply text-primary-500 text-xl; + @apply text-xl text-primary-500; } .subheading { @apply mb-16 text-4xl font-bold text-slate-800; diff --git a/tests/edit-options.spec.ts b/tests/edit-options.spec.ts index 184860ecf..09a48f585 100644 --- a/tests/edit-options.spec.ts +++ b/tests/edit-options.spec.ts @@ -5,7 +5,7 @@ test("should show warning when deleting options with votes in them", async ({ }) => { await page.goto("/demo"); - await expect(page.locator('text="Lunch Meeting Demo"')).toBeVisible(); + await expect(page.locator('text="Lunch Meeting"')).toBeVisible(); await page.click("text='Manage'"); await page.click("text='Edit options'"); diff --git a/tests/house-keeping.spec.ts b/tests/house-keeping.spec.ts index 34c36496b..8ad2306e5 100644 --- a/tests/house-keeping.spec.ts +++ b/tests/house-keeping.spec.ts @@ -16,61 +16,75 @@ test.beforeAll(async ({ request, baseURL }) => { // Active Poll { title: "Active Poll", - urlId: "active-poll", + id: "active-poll", type: "date", userId: "user1", + participantUrlId: "p1", + adminUrlId: "a1", }, // Poll that has been deleted 6 days ago { title: "Deleted poll", - urlId: "deleted-poll-6d", + id: "deleted-poll-6d", type: "date", userId: "user1", deleted: true, deletedAt: addDays(new Date(), -6), + participantUrlId: "p2", + adminUrlId: "a2", }, // Poll that has been deleted 7 days ago { title: "Deleted poll 7d", - urlId: "deleted-poll-7d", + id: "deleted-poll-7d", type: "date", userId: "user1", deleted: true, deletedAt: addDays(new Date(), -7), + participantUrlId: "p3", + adminUrlId: "a3", }, // Poll that has been inactive for 29 days { title: "Still active", - urlId: "still-active-poll", + id: "still-active-poll", type: "date", userId: "user1", touchedAt: addDays(new Date(), -29), + participantUrlId: "p4", + adminUrlId: "a4", }, // Poll that has been inactive for 30 days { title: "Inactive poll", - urlId: "inactive-poll", + id: "inactive-poll", type: "date", userId: "user1", touchedAt: addDays(new Date(), -30), + participantUrlId: "p5", + adminUrlId: "a5", }, // Demo poll { demo: true, title: "Demo poll", - urlId: "demo-poll-new", + id: "demo-poll-new", type: "date", userId: "user1", createdAt: new Date(), + participantUrlId: "p6", + adminUrlId: "a6", }, // Old demo poll { demo: true, title: "Demo poll", - urlId: "demo-poll-old", + id: "demo-poll-old", type: "date", userId: "user1", createdAt: addDays(new Date(), -2), + participantUrlId: "p7", + adminUrlId: "a7", }, ], }); @@ -138,7 +152,7 @@ test.beforeAll(async ({ request, baseURL }) => { test("should keep active polls", async () => { const poll = await prisma.poll.findUnique({ where: { - urlId: "active-poll", + id: "active-poll", }, }); @@ -150,7 +164,7 @@ test("should keep active polls", async () => { test("should keep polls that have been soft deleted for less than 7 days", async () => { const deletedPoll6d = await prisma.poll.findFirst({ where: { - urlId: "deleted-poll-6d", + id: "deleted-poll-6d", deleted: true, }, }); @@ -162,7 +176,7 @@ test("should keep polls that have been soft deleted for less than 7 days", async test("should hard delete polls that have been soft deleted for 7 days", async () => { const deletedPoll7d = await prisma.poll.findFirst({ where: { - urlId: "deleted-poll-7d", + id: "deleted-poll-7d", deleted: true, }, }); @@ -197,7 +211,7 @@ test("should hard delete polls that have been soft deleted for 7 days", async () test("should keep polls that are still active", async () => { const stillActivePoll = await prisma.poll.findUnique({ where: { - urlId: "still-active-poll", + id: "still-active-poll", }, }); @@ -208,7 +222,7 @@ test("should keep polls that are still active", async () => { test("should soft delete polls that are inactive", async () => { const inactivePoll = await prisma.poll.findFirst({ where: { - urlId: "inactive-poll", + id: "inactive-poll", deleted: true, }, }); @@ -221,7 +235,7 @@ test("should soft delete polls that are inactive", async () => { test("should keep new demo poll", async () => { const demoPoll = await prisma.poll.findFirst({ where: { - urlId: "demo-poll-new", + id: "demo-poll-new", }, }); @@ -231,7 +245,7 @@ test("should keep new demo poll", async () => { test("should delete old demo poll", async () => { const oldDemoPoll = await prisma.poll.findFirst({ where: { - urlId: "demo-poll-old", + id: "demo-poll-old", }, }); @@ -240,7 +254,7 @@ test("should delete old demo poll", async () => { // Teardown test.afterAll(async () => { - await prisma.$executeRaw`DELETE FROM polls WHERE url_id IN (${Prisma.join([ + await prisma.$executeRaw`DELETE FROM polls WHERE id IN (${Prisma.join([ "active-poll", "deleted-poll-6d", "deleted-poll-7d", diff --git a/tests/mobile-test.spec.ts b/tests/mobile-test.spec.ts index 7b83dd6e3..d34eb4d52 100644 --- a/tests/mobile-test.spec.ts +++ b/tests/mobile-test.spec.ts @@ -4,7 +4,7 @@ test("should be able to vote and comment on a poll", async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto("/demo"); - await expect(page.locator('text="Lunch Meeting Demo"')).toBeVisible(); + await expect(page.locator('text="Lunch Meeting"')).toBeVisible(); await page.click("text='New'"); await page.click("data-testid=poll-option >> nth=0"); diff --git a/tests/vote-and-comment.spec.ts b/tests/vote-and-comment.spec.ts index a922141ac..04f4cb1f6 100644 --- a/tests/vote-and-comment.spec.ts +++ b/tests/vote-and-comment.spec.ts @@ -9,7 +9,7 @@ test("should be able to vote and comment on a poll", async ({ page }) => { await page.goto("/demo"); - await expect(page.locator('text="Lunch Meeting Demo"')).toBeVisible(); + await expect(page.locator('text="Lunch Meeting"')).toBeVisible(); await page.type('[placeholder="Your name"]', "Test user"); // There is a hidden checkbox (nth=0) that exists so that the behaviour of the form is consistent even @@ -20,7 +20,7 @@ test("should be able to vote and comment on a poll", async ({ page }) => { await expect(page.locator("text='Test user'")).toBeVisible(); await expect(page.locator("text=Guest")).toBeVisible(); await expect( - page.locator("data-testid=participant-row >> nth=0").locator("text=You"), + page.locator("data-testid=participant-row >> nth=4").locator("text=You"), ).toBeVisible(); await page.type( "[placeholder='Thanks for the invite!']", diff --git a/yarn.lock b/yarn.lock index fda5601b7..b5994ce9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,13 +17,6 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@7.16.7", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== - dependencies: - "@babel/highlight" "^7.16.7" - "@babel/code-frame@^7.0.0": version "7.16.0" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz" @@ -31,32 +24,18 @@ dependencies: "@babel/highlight" "^7.16.0" +"@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.4", "@babel/compat-data@^7.16.8": version "7.17.0" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz" integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== -"@babel/core@7.16.12": - version "7.16.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.12.tgz#5edc53c1b71e54881315923ae2aedea2522bb784" - integrity sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.16.8" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helpers" "^7.16.7" - "@babel/parser" "^7.16.12" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.16.10" - "@babel/types" "^7.16.8" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" - "@babel/core@^7.15.5": version "7.17.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.0.tgz" @@ -78,15 +57,6 @@ json5 "^2.1.2" semver "^6.3.0" -"@babel/generator@^7.16.8", "@babel/generator@^7.17.3": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" - integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== - dependencies: - "@babel/types" "^7.17.0" - jsesc "^2.5.1" - source-map "^0.5.0" - "@babel/generator@^7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.0.tgz" @@ -228,7 +198,7 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-plugin-utils@7.16.7", "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz" integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== @@ -304,15 +274,6 @@ "@babel/traverse" "^7.16.8" "@babel/types" "^7.16.8" -"@babel/helpers@^7.16.7": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" - integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - "@babel/helpers@^7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.0.tgz" @@ -349,11 +310,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.16.12", "@babel/parser@^7.17.3": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" - integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== - "@babel/parser@^7.16.7", "@babel/parser@^7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.0.tgz" @@ -384,7 +340,7 @@ "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@7.16.7", "@babel/plugin-proposal-class-properties@^7.16.7": +"@babel/plugin-proposal-class-properties@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz" integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== @@ -401,7 +357,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@7.16.7", "@babel/plugin-proposal-dynamic-import@^7.16.7": +"@babel/plugin-proposal-dynamic-import@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz" integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== @@ -409,7 +365,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@7.16.7", "@babel/plugin-proposal-export-namespace-from@^7.16.7": +"@babel/plugin-proposal-export-namespace-from@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz" integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== @@ -425,7 +381,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@7.16.7", "@babel/plugin-proposal-logical-assignment-operators@^7.16.7": +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz" integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== @@ -433,7 +389,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@7.16.7", "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz" integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== @@ -441,7 +397,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@7.16.7", "@babel/plugin-proposal-numeric-separator@^7.16.7": +"@babel/plugin-proposal-numeric-separator@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz" integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== @@ -468,7 +424,7 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@7.16.7", "@babel/plugin-proposal-optional-chaining@^7.16.7": +"@babel/plugin-proposal-optional-chaining@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz" integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== @@ -477,7 +433,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@7.16.11", "@babel/plugin-proposal-private-methods@^7.16.11": +"@babel/plugin-proposal-private-methods@^7.16.11": version "7.16.11" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz" integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== @@ -485,7 +441,7 @@ "@babel/helper-create-class-features-plugin" "^7.16.10" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-private-property-in-object@7.16.7", "@babel/plugin-proposal-private-property-in-object@^7.16.7": +"@babel/plugin-proposal-private-property-in-object@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz" integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== @@ -503,7 +459,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-syntax-async-generators@7.8.4", "@babel/plugin-syntax-async-generators@^7.8.4": +"@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== @@ -538,7 +494,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-json-strings@7.8.3", "@babel/plugin-syntax-json-strings@^7.8.3": +"@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== @@ -573,14 +529,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@7.8.3", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== @@ -735,7 +691,7 @@ "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@7.16.8", "@babel/plugin-transform-modules-commonjs@^7.16.8": +"@babel/plugin-transform-modules-commonjs@^7.16.8": version "7.16.8" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz" integrity sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA== @@ -1017,7 +973,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.16.7" "@babel/plugin-transform-react-pure-annotations" "^7.16.7" -"@babel/preset-typescript@7.16.7", "@babel/preset-typescript@^7.15.0": +"@babel/preset-typescript@^7.15.0": version "7.16.7" resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz" integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== @@ -1094,22 +1050,6 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.16.10", "@babel/traverse@^7.17.3": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" - integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.3" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.3" - "@babel/types" "^7.17.0" - debug "^4.1.0" - globals "^11.1.0" - "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": version "7.17.0" resolved "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz" @@ -1228,17 +1168,6 @@ resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.5.0.tgz#483b44ba2c8b8d4391e1d2c863898d7dd0cc0296" integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ== -"@jest/types@^27.2.5", "@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - "@jridgewell/resolve-uri@^3.0.3": version "3.0.4" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz" @@ -1352,45 +1281,13 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" -"@playwright/test@^1.20.1": - version "1.20.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.20.1.tgz#6c0891271ec267fd513afdb7c6b6466cabed594c" - integrity sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q== +"@playwright/test@^1.22.2": + version "1.22.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.22.2.tgz#b848f25f8918140c2d0bae8e9227a40198f2dd4a" + integrity sha512-cCl96BEBGPtptFz7C2FOSN3PrTnJ3rPpENe+gYCMx4GNNDlN4tmo2D89y13feGKTMMAIVrXfSQ/UmaQKLy1XLA== dependencies: - "@babel/code-frame" "7.16.7" - "@babel/core" "7.16.12" - "@babel/helper-plugin-utils" "7.16.7" - "@babel/plugin-proposal-class-properties" "7.16.7" - "@babel/plugin-proposal-dynamic-import" "7.16.7" - "@babel/plugin-proposal-export-namespace-from" "7.16.7" - "@babel/plugin-proposal-logical-assignment-operators" "7.16.7" - "@babel/plugin-proposal-nullish-coalescing-operator" "7.16.7" - "@babel/plugin-proposal-numeric-separator" "7.16.7" - "@babel/plugin-proposal-optional-chaining" "7.16.7" - "@babel/plugin-proposal-private-methods" "7.16.11" - "@babel/plugin-proposal-private-property-in-object" "7.16.7" - "@babel/plugin-syntax-async-generators" "7.8.4" - "@babel/plugin-syntax-json-strings" "7.8.3" - "@babel/plugin-syntax-object-rest-spread" "7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "7.8.3" - "@babel/plugin-transform-modules-commonjs" "7.16.8" - "@babel/preset-typescript" "7.16.7" - colors "1.4.0" - commander "8.3.0" - debug "4.3.3" - expect "27.2.5" - jest-matcher-utils "27.2.5" - json5 "2.2.1" - mime "3.0.0" - minimatch "3.0.4" - ms "2.1.3" - open "8.4.0" - pirates "4.0.4" - playwright-core "1.20.1" - rimraf "3.0.2" - source-map-support "0.4.18" - stack-utils "2.0.5" - yazl "2.5.1" + "@types/node" "*" + playwright-core "1.22.2" "@polka/url@^1.0.0-next.20": version "1.0.0-next.21" @@ -1771,25 +1668,6 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - "@types/js-cookie@^2.2.6": version "2.2.7" resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz" @@ -1901,48 +1779,11 @@ resolved "https://registry.yarnpkg.com/@types/smoothscroll-polyfill/-/smoothscroll-polyfill-0.3.1.tgz#77fb3a6e116bdab4a5959122e3b8e201224dcd49" integrity sha512-+KkHw4y+EyeCtVXET7woHUhIbfWFCflc0E0mZnSV+ZdjPQeHt/9KPEuT7gSW/kFQ8O3EG30PLO++YhChDt8+Ag== -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - "@types/warning@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz" integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= -"@types/webidl-conversions@*": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" - integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== - -"@types/whatwg-url@^8.2.1": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.1.tgz#f1aac222dab7c59e011663a0cb0a3117b2ef05d4" - integrity sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ== - dependencies: - "@types/node" "*" - "@types/webidl-conversions" "*" - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^16.0.0": - version "16.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" - integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== - dependencies: - "@types/yargs-parser" "*" - -"@types/yauzl@^2.9.1": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" - integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.21.0": version "5.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz#bfc22e0191e6404ab1192973b3b4ea0461c1e878" @@ -2106,7 +1947,7 @@ acorn@^8.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -agent-base@6, agent-base@^6.0.2: +agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -2167,11 +2008,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" @@ -2366,11 +2202,6 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - big-integer@^1.6.16: version "1.6.51" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" @@ -2426,26 +2257,6 @@ browserslist@^4.17.5, browserslist@^4.19.1: node-releases "^2.0.1" picocolors "^1.0.0" -bson@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.2.tgz#3241c79d23d225b86ab6d2bc268b803d8a5fd444" - integrity sha512-VeJKHShcu1b/ugl0QiujlVuBepab714X9nNyBdA1kfekuDGecxgpTA2Z6nYbagrWFeiIyzSWIOzju3lhj+RNyQ== - dependencies: - buffer "^5.6.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -2553,16 +2364,6 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colors@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -commander@8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" @@ -2736,7 +2537,7 @@ date-fns@^2.28.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== -debug@4, debug@4.3.3: +debug@4: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -2764,7 +2565,7 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2781,11 +2582,6 @@ deepmerge@^4.2.2: resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" @@ -2803,11 +2599,6 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -denque@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" - integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== - detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -2827,11 +2618,6 @@ didyoumean@^1.2.2: resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -2924,13 +2710,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enquirer@^2.3.5: version "2.3.6" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" @@ -3029,11 +2808,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - eslint-config-next@12.1.0: version "12.1.0" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.0.tgz#8ace680dc5207e6ab6c915f3989adec122f582e7" @@ -3334,29 +3108,6 @@ eta@^1.12.3: resolved "https://registry.yarnpkg.com/eta/-/eta-1.12.3.tgz#2982d08adfbef39f9fa50e2fbd42d7337e7338b1" integrity sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg== -expect@27.2.5: - version "27.2.5" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.2.5.tgz#16154aaa60b4d9a5b0adacfea3e4d6178f4b93fd" - integrity sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA== - dependencies: - "@jest/types" "^27.2.5" - ansi-styles "^5.0.0" - jest-get-type "^27.0.6" - jest-matcher-utils "^27.2.5" - jest-message-util "^27.2.5" - jest-regex-util "^27.0.6" - -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -3400,13 +3151,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -3456,10 +3200,10 @@ fraction.js@^4.1.2: resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz" integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== -framer-motion@^6.2.9: - version "6.2.9" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.9.tgz#7de11140f57141ebaad4c99d6a492f00cb733d04" - integrity sha512-XMP4Z5j9KlGw8aeo7n8BXTJFbt1Vv5XRzHVOKiAna1yBG4SPwTdk/8bJRfztYb0Jmw90hzBTC/3Q2dhfdtXisQ== +framer-motion@^6.3.11: + version "6.3.11" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.3.11.tgz#c304ce9728601ad9377d47d5d9264e43d741d470" + integrity sha512-xQLk+ZSklNs5QNCUmdWPpKMOuWiB8ZETsvcIOWw8xvri9K3TamuifgCI/B6XpaEDR0/V2ZQF2Wm+gUAZrXo+rw== dependencies: framesync "6.0.1" hey-listen "^1.0.8" @@ -3524,13 +3268,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -3623,11 +3360,6 @@ graceful-fs@^4.1.2: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -3708,7 +3440,7 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" -https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== @@ -3733,11 +3465,6 @@ i18next@^21.6.12: dependencies: "@babel/runtime" "^7.17.2" -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz" @@ -3816,11 +3543,6 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - iron-session@^6.1.3: version "6.1.3" resolved "https://registry.yarnpkg.com/iron-session/-/iron-session-6.1.3.tgz#c900102560e7d19541a9e6b8bbabc5436b01a230" @@ -3885,11 +3607,6 @@ is-date-object@^1.0.1: resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz" integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -3988,13 +3705,6 @@ is-what@^4.1.6: resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.7.tgz#c41dc1d2d2d6a9285c624c2505f61849c8b1f9cc" integrity sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ== -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -4010,61 +3720,6 @@ isobject@^3.0.1: resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-diff@^27.2.5, jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== - dependencies: - chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-get-type@^27.0.6, jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== - -jest-matcher-utils@27.2.5: - version "27.2.5" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz#4684faaa8eb32bf15e6edaead6834031897e2980" - integrity sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg== - dependencies: - chalk "^4.0.0" - jest-diff "^27.2.5" - jest-get-type "^27.0.6" - pretty-format "^27.2.5" - -jest-matcher-utils@^27.2.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== - dependencies: - chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" - -jest-message-util@^27.2.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-regex-util@^27.0.6: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" - integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== - joi@^17.6.0: version "17.6.0" resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" @@ -4081,11 +3736,6 @@ jose@^4.5.1: resolved "https://registry.yarnpkg.com/jose/-/jose-4.5.1.tgz#1531d491fafe567338fd517f118293cce27aa0aa" integrity sha512-uhjuzZjKGKmi5DVJ/eghN8hJklLhwZRLRAaaG9MkkMAdfRYlHvZ10i4z/4WSN1hSTMffCet+k7iRHvbdGwmZPw== -jpeg-js@0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" - integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== - js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz" @@ -4144,11 +3794,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json5@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - json5@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" @@ -4325,11 +3970,6 @@ memoize-one@^5.1.1: resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== -memory-pager@^1.0.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" - integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -4348,17 +3988,12 @@ microseconds@0.2.0: resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz" integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== -mime@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" - integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== - mini-svg-data-uri@^1.2.3: version "1.4.3" resolved "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz" integrity sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -4389,26 +4024,6 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.5" -mongodb-connection-string-url@^2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz#f075c8d529e8d3916386018b8a396aed4f16e5ed" - integrity sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA== - dependencies: - "@types/whatwg-url" "^8.2.1" - whatwg-url "^11.0.0" - -mongodb@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.5.0.tgz#d74c2008567b606dccef220f62a44cd7b934eb92" - integrity sha512-A2l8MjEpKojnhbCM0MK3+UOGUSGvTNNSv7AkP1fsT7tkambrkkqN/5F2y+PhzsV0Nbv58u04TETpkaSEdI2zKA== - dependencies: - bson "^4.6.2" - denque "^2.0.1" - mongodb-connection-string-url "^2.5.2" - socks "^2.6.2" - optionalDependencies: - saslprep "^1.0.3" - mrmime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" @@ -4424,7 +4039,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -4670,22 +4285,13 @@ oblivious-set@1.0.0: resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz" integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -open@8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -4783,11 +4389,6 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -4803,18 +4404,6 @@ pify@^2.0.0: resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pirates@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" - integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== - -pixelmatch@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" - integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== - dependencies: - pngjs "^4.0.1" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz" @@ -4822,39 +4411,10 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" -playwright-core@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.20.1.tgz#2d892964dd3ddc93f6e185be4b59621f3a339d4c" - integrity sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA== - dependencies: - colors "1.4.0" - commander "8.3.0" - debug "4.3.3" - extract-zip "2.0.1" - https-proxy-agent "5.0.0" - jpeg-js "0.4.3" - mime "3.0.0" - pixelmatch "5.2.1" - pngjs "6.0.0" - progress "2.0.3" - proper-lockfile "4.1.2" - proxy-from-env "1.1.0" - rimraf "3.0.2" - socks-proxy-agent "6.1.1" - stack-utils "2.0.5" - ws "8.4.2" - yauzl "2.10.0" - yazl "2.5.1" - -pngjs@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" - integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== - -pngjs@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" - integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== +playwright-core@1.22.2: + version "1.22.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.22.2.tgz#ed2963d79d71c2a18d5a6fd25b60b9f0a344661a" + integrity sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q== popmotion@11.0.3: version "11.0.3" @@ -4943,15 +4503,6 @@ prettier@^2.3.0: resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz" integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== -pretty-format@^27.2.5, pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== - dependencies: - ansi-regex "^5.0.1" - ansi-styles "^5.0.0" - react-is "^17.0.1" - prisma@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.14.0.tgz#dd67ece37d7b5373e9fd9588971de0024b49be81" @@ -4964,7 +4515,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@2.0.3, progress@^2.0.0, progress@^2.0.3: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -4987,29 +4538,12 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proper-lockfile@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - -proxy-from-env@1.1.0, proxy-from-env@^1.1.0: +proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -5092,11 +4626,6 @@ react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" @@ -5332,11 +4861,6 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" @@ -5375,13 +4899,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -saslprep@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" - integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== - dependencies: - sparse-bitfield "^3.0.3" - scheduler@^0.20.2: version "0.20.2" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz" @@ -5448,7 +4965,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5476,51 +4993,22 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - smoothscroll-polyfill@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz#3a259131dc6930e6ca80003e1cb03b603b69abf8" integrity sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg== -socks-proxy-agent@6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" - integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== - dependencies: - agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" - -socks@^2.6.1, socks@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" - integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== - dependencies: - ip "^1.1.5" - smart-buffer "^4.2.0" - source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-support@0.4.18: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - source-map@0.5.6: version "0.5.6" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.0: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -5540,13 +5028,6 @@ spacetime@^7.1.2: resolved "https://registry.yarnpkg.com/spacetime/-/spacetime-7.1.2.tgz#73a94f0ba7f0c0b2230259b5ccf78dd9fd34cd16" integrity sha512-MUTgK9KU9gMXhZddwe0nlgFnCJXT4RfuIRqyo8VcUexZa94zkxk1WpVv3THgvMFz1+Hq9okoNiGTvj6qBdN4Cg== -sparse-bitfield@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" - integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= - dependencies: - memory-pager "^1.0.2" - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" @@ -5590,13 +5071,6 @@ stack-generator@^2.0.5: dependencies: stackframe "^1.1.1" -stack-utils@2.0.5, stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - stackframe@^1.1.1: version "1.2.0" resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz" @@ -5870,13 +5344,6 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -tr46@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" - integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== - dependencies: - punycode "^2.1.1" - tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -6060,11 +5527,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= -webidl-conversions@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" - integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== - webpack-bundle-analyzer@4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz#2f3c0ca9041d5ee47fa418693cf56b4a518b578b" @@ -6080,14 +5542,6 @@ webpack-bundle-analyzer@4.3.0: sirv "^1.0.7" ws "^7.3.1" -whatwg-url@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" - integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== - dependencies: - tr46 "^3.0.0" - webidl-conversions "^7.0.0" - whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -6131,11 +5585,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.4.2: - version "8.4.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" - integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== - ws@^7.3.1: version "7.5.7" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" @@ -6156,21 +5605,6 @@ yaml@^1.10.0, yaml@^1.10.2: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yauzl@2.10.0, yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yazl@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" - integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== - dependencies: - buffer-crc32 "~0.2.3" - zod@^3.16.0: version "3.16.0" resolved "https://registry.yarnpkg.com/zod/-/zod-3.16.0.tgz#edfdbf77fcc9a5af13a2630a44bdda5b90e759b7"