mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-16 16:05:33 +02:00
Add warning when deleting options with votes
This commit is contained in:
parent
ae9d9f1083
commit
654c300430
7 changed files with 150 additions and 20 deletions
23
components/confirm-prompt.tsx
Normal file
23
components/confirm-prompt.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import ReactDOM from "react-dom";
|
||||
import Modal, { ModalProps } from "./modal/modal";
|
||||
|
||||
export const confirmPrompt = (props: ModalProps) => {
|
||||
const div = document.createElement("div");
|
||||
document.body.appendChild(div);
|
||||
ReactDOM.render(
|
||||
<Modal
|
||||
okText="Ok"
|
||||
cancelText="Cancel"
|
||||
{...props}
|
||||
visible={true}
|
||||
onOk={() => {
|
||||
props.onOk?.();
|
||||
document.body.removeChild(div);
|
||||
}}
|
||||
onCancel={() => {
|
||||
document.body.removeChild(div);
|
||||
}}
|
||||
/>,
|
||||
div,
|
||||
);
|
||||
};
|
60
components/modal/modal-provider.tsx
Normal file
60
components/modal/modal-provider.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import * as React from "react";
|
||||
import { useList } from "react-use";
|
||||
import { useRequiredContext } from "../use-required-context";
|
||||
import Modal, { ModalProps } from "./modal";
|
||||
|
||||
export interface ModalProviderProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ModalContext =
|
||||
React.createContext<{
|
||||
render: (el: ModalProps) => void;
|
||||
} | null>(null);
|
||||
|
||||
ModalContext.displayName = "<ModalProvider />";
|
||||
|
||||
export const useModalContext = () => {
|
||||
return useRequiredContext(ModalContext);
|
||||
};
|
||||
|
||||
const ModalProvider: React.VoidFunctionComponent<ModalProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [modals, { push, removeAt, updateAt }] = useList<ModalProps>([]);
|
||||
|
||||
const removeModalAt = (index: number) => {
|
||||
updateAt(index, { ...modals[index], visible: false });
|
||||
setTimeout(() => {
|
||||
removeAt(index);
|
||||
}, 500);
|
||||
};
|
||||
return (
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
render: (props) => {
|
||||
push(props);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{modals.map((props, i) => (
|
||||
<Modal
|
||||
key={i}
|
||||
visible={true}
|
||||
{...props}
|
||||
onOk={() => {
|
||||
props.onOk?.();
|
||||
removeModalAt(i);
|
||||
}}
|
||||
onCancel={() => {
|
||||
props.onCancel?.();
|
||||
removeModalAt(i);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalProvider;
|
|
@ -13,8 +13,10 @@ import { decodeDateOption, encodeDateOption } from "utils/date-time-utils";
|
|||
import Dropdown, { DropdownItem } from "../dropdown";
|
||||
import { PollDetailsForm } from "../forms";
|
||||
import { useModal } from "../modal";
|
||||
import { useModalContext } from "../modal/modal-provider";
|
||||
import { usePoll } from "../use-poll";
|
||||
import { useUpdatePollMutation } from "./mutations";
|
||||
import { Trans } from "next-i18next";
|
||||
|
||||
const PollOptionsForm = React.lazy(() => import("../forms/poll-options-form"));
|
||||
|
||||
|
@ -25,6 +27,8 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
const { t } = useTranslation("app");
|
||||
const poll = usePoll();
|
||||
|
||||
const modalContext = useModalContext();
|
||||
|
||||
const { mutate: updatePollMutation, isLoading: isUpdating } =
|
||||
useUpdatePollMutation();
|
||||
const [
|
||||
|
@ -68,26 +72,52 @@ const ManagePoll: React.VoidFunctionComponent<{
|
|||
}}
|
||||
onSubmit={(data) => {
|
||||
const encodedOptions = data.options.map(encodeDateOption);
|
||||
const optionsToDelete = poll.options
|
||||
.filter((option) => {
|
||||
return !encodedOptions.includes(option.value);
|
||||
})
|
||||
.map((option) => option.id);
|
||||
const optionsToDelete = poll.options.filter((option) => {
|
||||
return !encodedOptions.includes(option.value);
|
||||
});
|
||||
|
||||
const optionsToAdd = encodedOptions.filter(
|
||||
(encodedOption) =>
|
||||
!poll.options.find((o) => o.value === encodedOption),
|
||||
);
|
||||
updatePollMutation(
|
||||
{
|
||||
timeZone: data.timeZone,
|
||||
optionsToDelete,
|
||||
optionsToAdd,
|
||||
},
|
||||
{
|
||||
onSuccess: () => closeChangeOptionsModal(),
|
||||
},
|
||||
|
||||
const onOk = () => {
|
||||
updatePollMutation(
|
||||
{
|
||||
timeZone: data.timeZone,
|
||||
optionsToDelete: optionsToDelete.map(({ id }) => id),
|
||||
optionsToAdd,
|
||||
},
|
||||
{
|
||||
onSuccess: () => closeChangeOptionsModal(),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const optionsToDeleteThatHaveVotes = optionsToDelete.filter(
|
||||
(option) => option.votes.length > 0,
|
||||
);
|
||||
|
||||
if (optionsToDeleteThatHaveVotes.length > 0) {
|
||||
modalContext.render({
|
||||
title: "Are you sure?",
|
||||
description: (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="deletingOptionsWarning"
|
||||
components={{ b: <strong /> }}
|
||||
/>
|
||||
),
|
||||
onOk,
|
||||
okButtonProps: {
|
||||
type: "danger",
|
||||
},
|
||||
okText: "Delete",
|
||||
cancelText: "Cancel",
|
||||
});
|
||||
} else {
|
||||
onOk();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</React.Suspense>
|
||||
|
|
14
components/use-required-context.ts
Normal file
14
components/use-required-context.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
|
||||
export const useRequiredContext = <T extends any>(
|
||||
context: React.Context<T | null>,
|
||||
errorMessage?: string,
|
||||
) => {
|
||||
const contextValue = React.useContext(context);
|
||||
if (contextValue === null) {
|
||||
throw new Error(
|
||||
errorMessage ?? `Missing context provider: ${context.displayName}`,
|
||||
);
|
||||
}
|
||||
return contextValue;
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import ModalProvider from "@/components/modal/modal-provider";
|
||||
import { NextPage } from "next";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import PlausibleProvider from "next-plausible";
|
||||
|
@ -42,9 +43,11 @@ const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => {
|
|||
</Head>
|
||||
<CrispChat />
|
||||
<Toaster />
|
||||
<UserNameContext.Provider value={sessionUserName}>
|
||||
<Component {...pageProps} />
|
||||
</UserNameContext.Provider>
|
||||
<ModalProvider>
|
||||
<UserNameContext.Provider value={sessionUserName}>
|
||||
<Component {...pageProps} />
|
||||
</UserNameContext.Provider>
|
||||
</ModalProvider>
|
||||
</QueryClientProvider>
|
||||
</PlausibleProvider>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import MobilePoll from "@/components/poll/mobile-poll";
|
|||
import { useUpdatePollMutation } from "@/components/poll/mutations";
|
||||
import NotificationsToggle from "@/components/poll/notifications-toggle";
|
||||
import PollSubheader from "@/components/poll/poll-subheader";
|
||||
import TruncatedLinkify from "@/components/poll/truncated-linkify";
|
||||
import { UserAvatarProvider } from "@/components/poll/user-avatar";
|
||||
import Popover from "@/components/popover";
|
||||
import Sharing from "@/components/sharing";
|
||||
|
@ -30,8 +31,6 @@ import { preventWidows } from "utils/prevent-widows";
|
|||
import { GetPollResponse } from "../api-client/get-poll";
|
||||
import { getBrowserTimeZone } from "../utils/date-time-utils";
|
||||
import Custom404 from "./404";
|
||||
import Linkify from "react-linkify";
|
||||
import TruncatedLinkify from "@/components/poll/truncated-linkify";
|
||||
|
||||
const Discussion = React.lazy(() => import("@/components/discussion"));
|
||||
|
||||
|
|
|
@ -41,5 +41,6 @@
|
|||
"participant": "Participant",
|
||||
"participantDescription": "Partial access to vote and comment on this poll.",
|
||||
"unverifiedMessage": "An email has been sent to <b>{{email}}</b> with a link to verify the email address.",
|
||||
"notificationsOnDescription": "An email will be sent to <b>{{email}}</b> when there is activity on this poll."
|
||||
"notificationsOnDescription": "An email will be sent to <b>{{email}}</b> when there is activity on this poll.",
|
||||
"deletingOptionsWarning": "You are deleting options that participants have voted for. Their votes will be also be deleted."
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue