mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-18 00:37:27 +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 Dropdown, { DropdownItem } from "../dropdown";
|
||||||
import { PollDetailsForm } from "../forms";
|
import { PollDetailsForm } from "../forms";
|
||||||
import { useModal } from "../modal";
|
import { useModal } from "../modal";
|
||||||
|
import { useModalContext } from "../modal/modal-provider";
|
||||||
import { usePoll } from "../use-poll";
|
import { usePoll } from "../use-poll";
|
||||||
import { useUpdatePollMutation } from "./mutations";
|
import { useUpdatePollMutation } from "./mutations";
|
||||||
|
import { Trans } from "next-i18next";
|
||||||
|
|
||||||
const PollOptionsForm = React.lazy(() => import("../forms/poll-options-form"));
|
const PollOptionsForm = React.lazy(() => import("../forms/poll-options-form"));
|
||||||
|
|
||||||
|
@ -25,6 +27,8 @@ const ManagePoll: React.VoidFunctionComponent<{
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const poll = usePoll();
|
const poll = usePoll();
|
||||||
|
|
||||||
|
const modalContext = useModalContext();
|
||||||
|
|
||||||
const { mutate: updatePollMutation, isLoading: isUpdating } =
|
const { mutate: updatePollMutation, isLoading: isUpdating } =
|
||||||
useUpdatePollMutation();
|
useUpdatePollMutation();
|
||||||
const [
|
const [
|
||||||
|
@ -68,26 +72,52 @@ const ManagePoll: React.VoidFunctionComponent<{
|
||||||
}}
|
}}
|
||||||
onSubmit={(data) => {
|
onSubmit={(data) => {
|
||||||
const encodedOptions = data.options.map(encodeDateOption);
|
const encodedOptions = data.options.map(encodeDateOption);
|
||||||
const optionsToDelete = poll.options
|
const optionsToDelete = poll.options.filter((option) => {
|
||||||
.filter((option) => {
|
return !encodedOptions.includes(option.value);
|
||||||
return !encodedOptions.includes(option.value);
|
});
|
||||||
})
|
|
||||||
.map((option) => option.id);
|
|
||||||
|
|
||||||
const optionsToAdd = encodedOptions.filter(
|
const optionsToAdd = encodedOptions.filter(
|
||||||
(encodedOption) =>
|
(encodedOption) =>
|
||||||
!poll.options.find((o) => o.value === encodedOption),
|
!poll.options.find((o) => o.value === encodedOption),
|
||||||
);
|
);
|
||||||
updatePollMutation(
|
|
||||||
{
|
const onOk = () => {
|
||||||
timeZone: data.timeZone,
|
updatePollMutation(
|
||||||
optionsToDelete,
|
{
|
||||||
optionsToAdd,
|
timeZone: data.timeZone,
|
||||||
},
|
optionsToDelete: optionsToDelete.map(({ id }) => id),
|
||||||
{
|
optionsToAdd,
|
||||||
onSuccess: () => closeChangeOptionsModal(),
|
},
|
||||||
},
|
{
|
||||||
|
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>
|
</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 { NextPage } from "next";
|
||||||
import { appWithTranslation } from "next-i18next";
|
import { appWithTranslation } from "next-i18next";
|
||||||
import PlausibleProvider from "next-plausible";
|
import PlausibleProvider from "next-plausible";
|
||||||
|
@ -42,9 +43,11 @@ const MyApp: NextPage<AppProps> = ({ Component, pageProps }) => {
|
||||||
</Head>
|
</Head>
|
||||||
<CrispChat />
|
<CrispChat />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<UserNameContext.Provider value={sessionUserName}>
|
<ModalProvider>
|
||||||
<Component {...pageProps} />
|
<UserNameContext.Provider value={sessionUserName}>
|
||||||
</UserNameContext.Provider>
|
<Component {...pageProps} />
|
||||||
|
</UserNameContext.Provider>
|
||||||
|
</ModalProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</PlausibleProvider>
|
</PlausibleProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import MobilePoll from "@/components/poll/mobile-poll";
|
||||||
import { useUpdatePollMutation } from "@/components/poll/mutations";
|
import { useUpdatePollMutation } from "@/components/poll/mutations";
|
||||||
import NotificationsToggle from "@/components/poll/notifications-toggle";
|
import NotificationsToggle from "@/components/poll/notifications-toggle";
|
||||||
import PollSubheader from "@/components/poll/poll-subheader";
|
import PollSubheader from "@/components/poll/poll-subheader";
|
||||||
|
import TruncatedLinkify from "@/components/poll/truncated-linkify";
|
||||||
import { UserAvatarProvider } from "@/components/poll/user-avatar";
|
import { UserAvatarProvider } from "@/components/poll/user-avatar";
|
||||||
import Popover from "@/components/popover";
|
import Popover from "@/components/popover";
|
||||||
import Sharing from "@/components/sharing";
|
import Sharing from "@/components/sharing";
|
||||||
|
@ -30,8 +31,6 @@ import { preventWidows } from "utils/prevent-widows";
|
||||||
import { GetPollResponse } from "../api-client/get-poll";
|
import { GetPollResponse } from "../api-client/get-poll";
|
||||||
import { getBrowserTimeZone } from "../utils/date-time-utils";
|
import { getBrowserTimeZone } from "../utils/date-time-utils";
|
||||||
import Custom404 from "./404";
|
import Custom404 from "./404";
|
||||||
import Linkify from "react-linkify";
|
|
||||||
import TruncatedLinkify from "@/components/poll/truncated-linkify";
|
|
||||||
|
|
||||||
const Discussion = React.lazy(() => import("@/components/discussion"));
|
const Discussion = React.lazy(() => import("@/components/discussion"));
|
||||||
|
|
||||||
|
|
|
@ -41,5 +41,6 @@
|
||||||
"participant": "Participant",
|
"participant": "Participant",
|
||||||
"participantDescription": "Partial access to vote and comment on this poll.",
|
"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.",
|
"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