⬆️ v3.0.0 (#704)

This commit is contained in:
Luke Vella 2023-06-19 17:17:00 +01:00 committed by GitHub
parent 735056f25f
commit c22b3abc4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
385 changed files with 19912 additions and 5250 deletions

View file

@ -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>
);
};