mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-29 10:16:32 +02:00
Add option to delete poll (#174)
This commit is contained in:
parent
2c4157ea24
commit
c170e03b6a
25 changed files with 271 additions and 104 deletions
|
@ -53,5 +53,8 @@
|
|||
"24h": "24-hour",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"ifNeedBe": "If need be"
|
||||
"ifNeedBe": "If need be",
|
||||
"areYouSure": "Are you sure?",
|
||||
"deletePollDescription": "All data related to this poll will be deleted. This action <b>cannot be undone</b>. To confirm, please type <s>“{{confirmText}}”</s> in to the input below:",
|
||||
"deletePoll": "Delete poll"
|
||||
}
|
||||
|
|
69
src/components/button.tsx
Normal file
69
src/components/button.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
|
||||
import SpinnerIcon from "@/components/icons/spinner.svg";
|
||||
|
||||
export interface ButtonProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
icon?: React.ReactElement;
|
||||
htmlType?: React.ButtonHTMLAttributes<HTMLButtonElement>["type"];
|
||||
type?: "default" | "primary" | "danger" | "link";
|
||||
form?: string;
|
||||
rounded?: boolean;
|
||||
title?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
function Button(
|
||||
{
|
||||
children,
|
||||
loading,
|
||||
type = "default",
|
||||
htmlType = "button",
|
||||
className,
|
||||
icon,
|
||||
disabled,
|
||||
rounded,
|
||||
...passThroughProps
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type={htmlType}
|
||||
className={clsx(
|
||||
{
|
||||
"btn-default": type === "default",
|
||||
"btn-primary": type === "primary",
|
||||
"btn-danger": type === "danger",
|
||||
"btn-link": type === "link",
|
||||
"btn-disabled": disabled,
|
||||
"h-auto rounded-full p-2": rounded,
|
||||
"w-10 p-0": !children,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...passThroughProps}
|
||||
disabled={disabled || loading}
|
||||
>
|
||||
{loading ? (
|
||||
<SpinnerIcon
|
||||
className={clsx("inline-block w-5 animate-spin", {
|
||||
"mr-2": !!children,
|
||||
})}
|
||||
/>
|
||||
) : icon ? (
|
||||
React.cloneElement(icon, {
|
||||
className: clsx("w-5 h-5", { "-ml-1 mr-2": !!children }),
|
||||
})
|
||||
) : null}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
|
@ -1,69 +0,0 @@
|
|||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
|
||||
import SpinnerIcon from "../icons/spinner.svg";
|
||||
|
||||
export interface ButtonProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
icon?: React.ReactElement;
|
||||
htmlType?: React.ButtonHTMLAttributes<HTMLButtonElement>["type"];
|
||||
type?: "default" | "primary" | "danger" | "link";
|
||||
form?: string;
|
||||
rounded?: boolean;
|
||||
title?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
const Button: React.ForwardRefRenderFunction<HTMLButtonElement, ButtonProps> = (
|
||||
{
|
||||
children,
|
||||
loading,
|
||||
type = "default",
|
||||
htmlType = "button",
|
||||
className,
|
||||
icon,
|
||||
disabled,
|
||||
rounded,
|
||||
...passThroughProps
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type={htmlType}
|
||||
className={clsx(
|
||||
{
|
||||
"btn-default": type === "default",
|
||||
"btn-primary": type === "primary",
|
||||
"btn-danger": type === "danger",
|
||||
"btn-link": type === "link",
|
||||
"btn-disabled": disabled,
|
||||
"h-auto rounded-full p-2": rounded,
|
||||
"w-10 p-0": !children,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...passThroughProps}
|
||||
disabled={disabled || loading}
|
||||
>
|
||||
{loading ? (
|
||||
<SpinnerIcon
|
||||
className={clsx("inline-block w-5 animate-spin", {
|
||||
"mr-2": !!children,
|
||||
})}
|
||||
/>
|
||||
) : icon ? (
|
||||
React.cloneElement(icon, {
|
||||
className: clsx("w-5 h-5", { "-ml-1 mr-2": !!children }),
|
||||
})
|
||||
) : null}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.forwardRef(Button);
|
|
@ -1,2 +0,0 @@
|
|||
export type { ButtonProps } from "./button";
|
||||
export { default } from "./button";
|
|
@ -8,7 +8,7 @@ import { useSessionStorage } from "react-use";
|
|||
|
||||
import { encodeDateOption } from "../utils/date-time-utils";
|
||||
import { trpc } from "../utils/trpc";
|
||||
import Button from "./button";
|
||||
import { Button } from "./button";
|
||||
import {
|
||||
NewEventData,
|
||||
PollDetailsData,
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
|
||||
import Chat from "@/components/icons/chat.svg";
|
||||
|
||||
import Button from "./button";
|
||||
import { Button } from "./button";
|
||||
|
||||
const crispWebsiteId = process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||
|
||||
import { requiredString } from "../../utils/form-validation";
|
||||
import { trpc } from "../../utils/trpc";
|
||||
import Button from "../button";
|
||||
import { Button } from "../button";
|
||||
import CompactButton from "../compact-button";
|
||||
import Dropdown, { DropdownItem } from "../dropdown";
|
||||
import DotsHorizontal from "../icons/dots-horizontal.svg";
|
||||
|
|
|
@ -2,7 +2,7 @@ import Head from "next/head";
|
|||
import Link from "next/link";
|
||||
import * as React from "react";
|
||||
|
||||
import Button from "@/components/button";
|
||||
import { Button } from "@/components/button";
|
||||
import Chat from "@/components/icons/chat.svg";
|
||||
import EmojiSad from "@/components/icons/emoji-sad.svg";
|
||||
|
||||
|
@ -28,7 +28,7 @@ const ErrorPage: React.VoidFunctionComponent<ComponentProps> = ({
|
|||
<div className="flex items-start">
|
||||
<div className="text-center">
|
||||
<Icon className="mb-4 inline-block w-24 text-slate-400" />
|
||||
<div className="text-3xl font-bold uppercase text-indigo-500 ">
|
||||
<div className="mb-2 text-3xl font-bold text-indigo-500 ">
|
||||
{title}
|
||||
</div>
|
||||
<p>{description}</p>
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
getDateProps,
|
||||
removeAllOptionsForDay,
|
||||
} from "../../../../utils/date-time-utils";
|
||||
import Button from "../../../button";
|
||||
import { Button } from "../../../button";
|
||||
import CompactButton from "../../../compact-button";
|
||||
import DateCard from "../../../date-card";
|
||||
import Dropdown, { DropdownItem } from "../../../dropdown";
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 329 B |
|
@ -4,7 +4,7 @@ import { usePlausible } from "next-plausible";
|
|||
import * as React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import Button from "@/components/button";
|
||||
import { Button } from "@/components/button";
|
||||
import Magic from "@/components/icons/magic.svg";
|
||||
import { validEmail } from "@/utils/form-validation";
|
||||
|
||||
|
|
|
@ -8,9 +8,15 @@ export interface ModalProviderProps {
|
|||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
type ModalContentProps = { close: () => void };
|
||||
|
||||
interface ModalConfig extends ModalProps {
|
||||
content?: React.ReactNode | ((props: ModalContentProps) => React.ReactNode);
|
||||
}
|
||||
|
||||
const ModalContext =
|
||||
React.createContext<{
|
||||
render: (el: ModalProps) => void;
|
||||
render: (el: ModalConfig) => void;
|
||||
} | null>(null);
|
||||
|
||||
ModalContext.displayName = "<ModalProvider />";
|
||||
|
@ -22,7 +28,7 @@ export const useModalContext = () => {
|
|||
const ModalProvider: React.VoidFunctionComponent<ModalProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [modals, { push, removeAt, updateAt }] = useList<ModalProps>([]);
|
||||
const [modals, { push, removeAt, updateAt }] = useList<ModalConfig>([]);
|
||||
|
||||
const removeModalAt = (index: number) => {
|
||||
updateAt(index, { ...modals[index], visible: false });
|
||||
|
@ -44,6 +50,11 @@ const ModalProvider: React.VoidFunctionComponent<ModalProviderProps> = ({
|
|||
key={i}
|
||||
visible={true}
|
||||
{...props}
|
||||
content={
|
||||
typeof props.content === "function"
|
||||
? props.content({ close: () => removeModalAt(i) })
|
||||
: props.content
|
||||
}
|
||||
onOk={() => {
|
||||
props.onOk?.();
|
||||
removeModalAt(i);
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as React from "react";
|
|||
|
||||
import X from "@/components/icons/x.svg";
|
||||
|
||||
import Button, { ButtonProps } from "../button";
|
||||
import { Button, ButtonProps } from "../button";
|
||||
|
||||
export interface ModalProps {
|
||||
description?: React.ReactNode;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Participant, Vote, VoteType } from "@prisma/client";
|
|||
import { keyBy } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import Trash from "@/components/icons/trash.svg";
|
||||
import {
|
||||
decodeOptions,
|
||||
getBrowserTimeZone,
|
||||
|
@ -10,6 +11,7 @@ import {
|
|||
} from "@/utils/date-time-utils";
|
||||
import { GetPollApiResponse } from "@/utils/trpc/types";
|
||||
|
||||
import ErrorPage from "./error-page";
|
||||
import { useParticipants } from "./participants-provider";
|
||||
import { usePreferences } from "./preferences/use-preferences";
|
||||
import { useSession } from "./session";
|
||||
|
@ -22,6 +24,8 @@ type PollContextValue = {
|
|||
setTargetTimeZone: (timeZone: string) => void;
|
||||
pollType: "date" | "timeSlot";
|
||||
highScore: number;
|
||||
isDeleted: boolean;
|
||||
setDeleted: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
optionIds: string[];
|
||||
// TODO (Luke Vella) [2022-05-18]: Move this stuff to participants provider
|
||||
getParticipantsWhoVotedForOption: (optionId: string) => Participant[]; // maybe just attach votes to parsed options
|
||||
|
@ -49,7 +53,7 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
children?: React.ReactNode;
|
||||
}> = ({ value: poll, children }) => {
|
||||
const { participants } = useParticipants();
|
||||
|
||||
const [isDeleted, setDeleted] = React.useState(false);
|
||||
const { user } = useSession();
|
||||
const [targetTimeZone, setTargetTimeZone] =
|
||||
React.useState(getBrowserTimeZone);
|
||||
|
@ -145,9 +149,20 @@ export const PollContextProvider: React.VoidFunctionComponent<{
|
|||
...parsedOptions,
|
||||
targetTimeZone,
|
||||
setTargetTimeZone,
|
||||
isDeleted,
|
||||
setDeleted,
|
||||
};
|
||||
}, [getScore, locale, participants, poll, targetTimeZone, user]);
|
||||
}, [getScore, isDeleted, locale, participants, poll, targetTimeZone, user]);
|
||||
|
||||
if (isDeleted) {
|
||||
return (
|
||||
<ErrorPage
|
||||
icon={Trash}
|
||||
title="Deleted poll"
|
||||
description="This poll doesn't exist anymore."
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PollContext.Provider value={contextValue}>{children}</PollContext.Provider>
|
||||
);
|
||||
|
|
|
@ -6,7 +6,7 @@ import React from "react";
|
|||
import toast from "react-hot-toast";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
import Button from "@/components/button";
|
||||
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";
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as React from "react";
|
|||
import { useMeasure } from "react-use";
|
||||
import smoothscroll from "smoothscroll-polyfill";
|
||||
|
||||
import Button from "../button";
|
||||
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";
|
||||
|
|
|
@ -7,7 +7,7 @@ import Check from "@/components/icons/check.svg";
|
|||
import X from "@/components/icons/x.svg";
|
||||
|
||||
import { requiredString } from "../../../utils/form-validation";
|
||||
import Button from "../../button";
|
||||
import { Button } from "../../button";
|
||||
import NameInput from "../../name-input";
|
||||
import { usePoll } from "../../poll-context";
|
||||
import { normalizeVotes } from "../mutations";
|
||||
|
|
|
@ -2,13 +2,14 @@ import { Placement } from "@floating-ui/react-dom-interactions";
|
|||
import { Trans, useTranslation } from "next-i18next";
|
||||
import * as React from "react";
|
||||
|
||||
import Button from "@/components/button";
|
||||
import { Button } from "@/components/button";
|
||||
import Cog from "@/components/icons/cog.svg";
|
||||
import LockClosed from "@/components/icons/lock-closed.svg";
|
||||
import LockOpen from "@/components/icons/lock-open.svg";
|
||||
import Pencil from "@/components/icons/pencil-alt.svg";
|
||||
import Save from "@/components/icons/save.svg";
|
||||
import Table from "@/components/icons/table.svg";
|
||||
import Trash from "@/components/icons/trash.svg";
|
||||
import { encodeDateOption } from "@/utils/date-time-utils";
|
||||
|
||||
import Dropdown, { DropdownItem } from "../dropdown";
|
||||
|
@ -16,6 +17,7 @@ import { PollDetailsForm } from "../forms";
|
|||
import { useModal } from "../modal";
|
||||
import { useModalContext } from "../modal/modal-provider";
|
||||
import { usePoll } from "../poll-context";
|
||||
import { DeletePollForm } from "./manage-poll/delete-poll-form";
|
||||
import { useCsvExporter } from "./manage-poll/use-csv-exporter";
|
||||
import { useUpdatePollMutation } from "./mutations";
|
||||
|
||||
|
@ -25,7 +27,7 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
placement?: Placement;
|
||||
}> = ({ placement }) => {
|
||||
const { t } = useTranslation("app");
|
||||
const { poll, getParticipantsWhoVotedForOption } = usePoll();
|
||||
const { poll, getParticipantsWhoVotedForOption, setDeleted } = usePoll();
|
||||
|
||||
const { exportToCsv } = useCsvExporter();
|
||||
|
||||
|
@ -206,6 +208,26 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
}
|
||||
/>
|
||||
)}
|
||||
<DropdownItem
|
||||
icon={Trash}
|
||||
label="Delete poll"
|
||||
onClick={() => {
|
||||
modalContext.render({
|
||||
overlayClosable: true,
|
||||
content: ({ close }) => (
|
||||
<DeletePollForm
|
||||
onConfirm={async () => {
|
||||
close();
|
||||
setDeleted(true);
|
||||
}}
|
||||
onCancel={close}
|
||||
urlId={poll.urlId}
|
||||
/>
|
||||
),
|
||||
footer: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
|
|
89
src/components/poll/manage-poll/delete-poll-form.tsx
Normal file
89
src/components/poll/manage-poll/delete-poll-form.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import clsx from "clsx";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Button } from "@/components/button";
|
||||
import Exclamation from "@/components/icons/exclamation.svg";
|
||||
|
||||
import { trpc } from "../../../utils/trpc";
|
||||
|
||||
const confirmText = "delete-me";
|
||||
|
||||
export const DeletePollForm: React.VoidFunctionComponent<{
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
urlId: string;
|
||||
}> = ({ onCancel, onConfirm, urlId }) => {
|
||||
const { register, handleSubmit, formState, watch } =
|
||||
useForm<{ confirmation: string }>();
|
||||
|
||||
const plausible = usePlausible();
|
||||
|
||||
const confirmationText = watch("confirmation");
|
||||
const canDelete = confirmationText === confirmText;
|
||||
const deletePoll = trpc.useMutation("polls.delete", {
|
||||
onSuccess: () => {
|
||||
plausible("Deleted poll");
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useTranslation("app");
|
||||
|
||||
return (
|
||||
<div className="flex max-w-lg space-x-6 p-5">
|
||||
<div className="">
|
||||
<div className="rounded-full bg-rose-100 p-3">
|
||||
<Exclamation className="w-8 text-rose-500" />
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
data-testid="delete-poll-form"
|
||||
onSubmit={handleSubmit(async () => {
|
||||
await deletePoll.mutateAsync({ urlId });
|
||||
onConfirm();
|
||||
})}
|
||||
>
|
||||
<div className="mb-3 text-xl font-medium text-slate-800">
|
||||
{t("areYouSure")}
|
||||
</div>
|
||||
<p className="text-slate-500">
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="deletePollDescription"
|
||||
values={{ confirmText }}
|
||||
components={{
|
||||
b: <strong />,
|
||||
s: <span className="whitespace-nowrap" />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<div className="mb-6">
|
||||
<input
|
||||
type="text"
|
||||
className={clsx("input w-full", {
|
||||
"input-error": formState.errors.confirmation,
|
||||
})}
|
||||
placeholder={confirmText}
|
||||
{...register("confirmation", {
|
||||
validate: (value) => value === confirmText,
|
||||
})}
|
||||
readOnly={formState.isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Button onClick={onCancel}>{t("cancel")}</Button>
|
||||
<Button
|
||||
disabled={!canDelete}
|
||||
htmlType="submit"
|
||||
type="danger"
|
||||
loading={formState.isSubmitting}
|
||||
>
|
||||
{t("deletePoll")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -15,7 +15,7 @@ import Trash from "@/components/icons/trash.svg";
|
|||
import { usePoll } from "@/components/poll-context";
|
||||
|
||||
import { requiredString } from "../../utils/form-validation";
|
||||
import Button from "../button";
|
||||
import { Button } from "../button";
|
||||
import { styleMenuItem } from "../menu-styles";
|
||||
import NameInput from "../name-input";
|
||||
import { useParticipants } from "../participants-provider";
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Trans, useTranslation } from "next-i18next";
|
|||
import { usePlausible } from "next-plausible";
|
||||
import * as React from "react";
|
||||
|
||||
import Button from "@/components/button";
|
||||
import { Button } from "@/components/button";
|
||||
import Bell from "@/components/icons/bell.svg";
|
||||
import BellCrossed from "@/components/icons/bell-crossed.svg";
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as React from "react";
|
|||
|
||||
import { trpc } from "../../utils/trpc";
|
||||
import Badge from "../badge";
|
||||
import Button from "../button";
|
||||
import { Button } from "../button";
|
||||
import { usePoll } from "../poll-context";
|
||||
import Popover from "../popover";
|
||||
import { usePreferences } from "../preferences/use-preferences";
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as React from "react";
|
|||
import toast from "react-hot-toast";
|
||||
import { useCopyToClipboard } from "react-use";
|
||||
|
||||
import Button from "./button";
|
||||
import { Button } from "./button";
|
||||
|
||||
export interface SharingProps {
|
||||
links: Link[];
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { prisma } from "~/prisma/db";
|
||||
|
||||
import { absoluteUrl } from "../../utils/absolute-url";
|
||||
import { sendEmailTemplate } from "../../utils/api-utils";
|
||||
import { createToken } from "../../utils/auth";
|
||||
|
@ -199,4 +201,20 @@ export const polls = createRouter()
|
|||
|
||||
return createPollResponse(poll, link);
|
||||
},
|
||||
})
|
||||
.mutation("delete", {
|
||||
input: z.object({
|
||||
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 } });
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("should be able to create a new poll", async ({ page, context }) => {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: "rallly_cookie_consent",
|
||||
value: "1",
|
||||
url: "http://localhost",
|
||||
},
|
||||
]);
|
||||
test("should be able to create a new poll and delete it", async ({ page }) => {
|
||||
await page.goto("/new");
|
||||
// // // Find an element with the text 'About Page' and click on it
|
||||
await page.type('[placeholder="Monthly Meetup"]', "Monthly Meetup");
|
||||
// click on label to focus on input
|
||||
await page.click('text="Location"');
|
||||
|
@ -23,6 +15,7 @@ test("should be able to create a new poll", async ({ page, context }) => {
|
|||
|
||||
await page.click('[title="Next month"]');
|
||||
|
||||
// Select a few days
|
||||
await page.click("text=/^5$/");
|
||||
await page.click("text=/^7$/");
|
||||
await page.click("text=/^10$/");
|
||||
|
@ -38,4 +31,22 @@ test("should be able to create a new poll", async ({ page, context }) => {
|
|||
await expect(page.locator("data-testid=poll-title")).toHaveText(
|
||||
"Monthly Meetup",
|
||||
);
|
||||
|
||||
// let's delete the poll we just created
|
||||
await page.click("text=Manage");
|
||||
await page.click("text=Delete poll");
|
||||
|
||||
const deletePollForm = page.locator("data-testid=delete-poll-form");
|
||||
|
||||
// button should be disabled
|
||||
await expect(deletePollForm.locator("text=Delete poll")).toBeDisabled();
|
||||
|
||||
// enter confirmation text
|
||||
await page.type("[placeholder=delete-me]", "delete-me");
|
||||
|
||||
// button should now be enabled
|
||||
await deletePollForm.locator("text=Delete poll").click();
|
||||
|
||||
// expect delete message to appear
|
||||
await expect(page.locator("text=Deleted poll")).toBeVisible();
|
||||
});
|
Loading…
Add table
Reference in a new issue