mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-02 18:51:52 +02:00
⬆️ v3.0.0 (#704)
This commit is contained in:
parent
735056f25f
commit
c22b3abc4d
385 changed files with 19912 additions and 5250 deletions
|
@ -1,21 +1,38 @@
|
|||
import { trpc } from "@rallly/backend";
|
||||
import { PencilIcon, TagIcon, TrashIcon } from "@rallly/icons";
|
||||
import { Button } from "@rallly/ui/button";
|
||||
import {
|
||||
DotsHorizontalIcon,
|
||||
PencilIcon,
|
||||
TagIcon,
|
||||
TrashIcon,
|
||||
} from "@rallly/icons";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@rallly/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuItemIconLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from "@rallly/ui/dropdown-menu";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@rallly/ui/form";
|
||||
import { Input } from "@rallly/ui/input";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
import { Button } from "@/components/button";
|
||||
import Dropdown, { DropdownItem } from "@/components/dropdown";
|
||||
import Modal from "@/components/modal/modal";
|
||||
import { useModalState } from "@/components/modal/use-modal";
|
||||
import { useDeleteParticipantModal } from "@/components/poll/use-delete-participant-modal";
|
||||
import { TextInput } from "@/components/text-input";
|
||||
import { useDeleteParticipantMutation } from "@/components/poll/mutations";
|
||||
import { Trans } from "@/components/trans";
|
||||
import { useFormValidation } from "@/utils/form-validation";
|
||||
import { usePostHog } from "@/utils/posthog";
|
||||
|
||||
|
@ -24,67 +41,120 @@ import { Participant } from ".prisma/client";
|
|||
export const ParticipantDropdown = ({
|
||||
participant,
|
||||
onEdit,
|
||||
children,
|
||||
disabled,
|
||||
align,
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
participant: Participant;
|
||||
align?: "start" | "end";
|
||||
onEdit: () => void;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const confirmDeleteParticipant = useDeleteParticipantModal();
|
||||
|
||||
const [isChangeNameModalVisible, showChangeNameModal, hideChangeNameModal] =
|
||||
useModalState();
|
||||
const [isChangeNameModalVisible, setIsChangeNameModalVisible] =
|
||||
React.useState(false);
|
||||
const [isDeleteParticipantModalVisible, setIsDeleteParticipantModalVisible] =
|
||||
React.useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
placement="bottom-start"
|
||||
trigger={
|
||||
<Button data-testid="participant-menu">
|
||||
<DotsHorizontalIcon className="h-4 text-slate-500" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<DropdownItem
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
disabled={disabled}
|
||||
icon={PencilIcon}
|
||||
onClick={onEdit}
|
||||
label={t("editVotes")}
|
||||
/>
|
||||
<DropdownItem
|
||||
disabled={disabled}
|
||||
icon={TagIcon}
|
||||
onClick={showChangeNameModal}
|
||||
label={t("changeName")}
|
||||
/>
|
||||
<DropdownItem
|
||||
disabled={disabled}
|
||||
icon={TrashIcon}
|
||||
onClick={() =>
|
||||
confirmDeleteParticipant(participant.id, participant.name)
|
||||
}
|
||||
label={t("delete")}
|
||||
/>
|
||||
</Dropdown>
|
||||
<Modal
|
||||
showClose={true}
|
||||
overlayClosable={true}
|
||||
visible={isChangeNameModalVisible}
|
||||
onCancel={hideChangeNameModal}
|
||||
footer={null}
|
||||
content={
|
||||
<ChangeNameModal
|
||||
oldName={participant.name}
|
||||
onDone={hideChangeNameModal}
|
||||
participantId={participant.id}
|
||||
/>
|
||||
}
|
||||
asChild={true}
|
||||
data-testid="participant-menu"
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align={align}>
|
||||
<DropdownMenuItem onClick={onEdit}>
|
||||
<DropdownMenuItemIconLabel icon={PencilIcon}>
|
||||
<Trans i18nKey="editVotes" />
|
||||
</DropdownMenuItemIconLabel>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setIsChangeNameModalVisible(true)}>
|
||||
<DropdownMenuItemIconLabel icon={TagIcon}>
|
||||
<Trans i18nKey="changeName" />
|
||||
</DropdownMenuItemIconLabel>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setIsDeleteParticipantModalVisible(true)}
|
||||
>
|
||||
<DropdownMenuItemIconLabel icon={TrashIcon}>
|
||||
<Trans i18nKey="delete" />
|
||||
</DropdownMenuItemIconLabel>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<ChangeNameModal
|
||||
open={isChangeNameModalVisible}
|
||||
onOpenChange={setIsChangeNameModalVisible}
|
||||
oldName={participant.name}
|
||||
participantId={participant.id}
|
||||
/>
|
||||
<DeleteParticipantModal
|
||||
open={isDeleteParticipantModalVisible}
|
||||
onOpenChange={setIsDeleteParticipantModalVisible}
|
||||
participantId={participant.id}
|
||||
participantName={participant.name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DeleteParticipantModal = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
participantId,
|
||||
participantName,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
participantId: string;
|
||||
participantName: string;
|
||||
}) => {
|
||||
const deleteParticipant = useDeleteParticipantMutation();
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans
|
||||
i18nKey="deleteParticipant"
|
||||
values={{ name: participantName }}
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans i18nKey="deleteParticipantDescription" />
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
loading={deleteParticipant.isLoading}
|
||||
variant="destructive"
|
||||
onClick={async () => {
|
||||
deleteParticipant.mutate({
|
||||
participantId,
|
||||
});
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="delete" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
type ChangeNameForm = {
|
||||
name: string;
|
||||
};
|
||||
|
@ -92,7 +162,8 @@ type ChangeNameForm = {
|
|||
const ChangeNameModal = (props: {
|
||||
oldName: string;
|
||||
participantId: string;
|
||||
onDone: () => void;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) => {
|
||||
const posthog = usePostHog();
|
||||
const queryClient = trpc.useContext();
|
||||
|
@ -106,14 +177,13 @@ const ChangeNameModal = (props: {
|
|||
});
|
||||
},
|
||||
});
|
||||
const { register, handleSubmit, setFocus, formState } =
|
||||
useForm<ChangeNameForm>({
|
||||
defaultValues: {
|
||||
name: props.oldName,
|
||||
},
|
||||
});
|
||||
const form = useForm<ChangeNameForm>({
|
||||
defaultValues: {
|
||||
name: props.oldName,
|
||||
},
|
||||
});
|
||||
|
||||
const { errors } = formState;
|
||||
const { control, reset, handleSubmit, setFocus, formState } = form;
|
||||
|
||||
useMount(() => {
|
||||
setFocus("name", {
|
||||
|
@ -130,49 +200,66 @@ const ChangeNameModal = (props: {
|
|||
newName: name,
|
||||
});
|
||||
}
|
||||
props.onDone();
|
||||
props.onOpenChange(false);
|
||||
},
|
||||
[changeName, formState.isDirty, props],
|
||||
);
|
||||
|
||||
const { requiredString } = useFormValidation();
|
||||
|
||||
const formName = `change-name-${props.participantId}`;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handler)} className="max-w-sm space-y-3 p-4">
|
||||
<div>
|
||||
<div className="text-lg font-semibold text-slate-800">
|
||||
{t("changeName")}
|
||||
</div>
|
||||
<div>{t("changeNameDescription")}</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<label className="mb-1 text-slate-500">{t("name")}</label>
|
||||
<TextInput
|
||||
className="w-full"
|
||||
error={!!errors.name}
|
||||
disabled={formState.isSubmitting}
|
||||
{...register("name", {
|
||||
validate: requiredString(t("name")),
|
||||
})}
|
||||
/>
|
||||
{errors.name ? (
|
||||
<div className="text-sm text-rose-500">{errors.name.message}</div>
|
||||
) : null}
|
||||
<div className="mt-2 text-sm text-slate-500">{t("changeNameInfo")}</div>
|
||||
</fieldset>
|
||||
<div className="flex gap-2 ">
|
||||
<Button disabled={formState.isSubmitting} onClick={props.onDone}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={formState.isSubmitting}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
>
|
||||
{t("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("changeName")}</DialogTitle>
|
||||
<DialogDescription>{t("changeNameDescription")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form id={formName} onSubmit={handleSubmit(handler)}>
|
||||
<FormField
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{
|
||||
validate: requiredString(t("name")),
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full"
|
||||
{...field}
|
||||
disabled={formState.isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>{t("changeNameInfo")}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
disabled={formState.isSubmitting}
|
||||
onClick={() => {
|
||||
reset();
|
||||
props.onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
form={formName}
|
||||
loading={formState.isSubmitting}
|
||||
type="submit"
|
||||
variant="primary"
|
||||
>
|
||||
{t("save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue