mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 09:59:00 +02:00
✨ Add ability to change participant's name (#577)
This commit is contained in:
parent
05d2c7b1d0
commit
cb52adab01
15 changed files with 406 additions and 261 deletions
|
@ -2,6 +2,7 @@ import { VoteType } from "@rallly/database";
|
|||
import clsx from "clsx";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
import { useFormValidation } from "../utils/form-validation";
|
||||
import { Button } from "./button";
|
||||
|
@ -26,52 +27,60 @@ const VoteSummary = ({
|
|||
votes,
|
||||
className,
|
||||
}: {
|
||||
className: string;
|
||||
className?: string;
|
||||
votes: { optionId: string; type: VoteType }[];
|
||||
}) => {
|
||||
const { t } = useTranslation("app");
|
||||
const voteByType = votes.reduce<Record<VoteType, number>>(
|
||||
const voteByType = votes.reduce<Record<VoteType, string[]>>(
|
||||
(acc, vote) => {
|
||||
acc[vote.type] = acc[vote.type] ? acc[vote.type] + 1 : 1;
|
||||
acc[vote.type] = [...acc[vote.type], vote.optionId];
|
||||
return acc;
|
||||
},
|
||||
{ yes: 0, ifNeedBe: 0, no: 0 },
|
||||
{ yes: [], ifNeedBe: [], no: [] },
|
||||
);
|
||||
|
||||
const voteTypes = Object.keys(voteByType) as VoteType[];
|
||||
|
||||
return (
|
||||
<div className={clsx("space-y-1", className)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoteIcon type="yes" />
|
||||
<div>{t("yes")}</div>
|
||||
<div className="rounded bg-white px-2 text-sm shadow-sm">
|
||||
{voteByType["yes"]}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoteIcon type="ifNeedBe" />
|
||||
<div>{t("ifNeedBe")}</div>
|
||||
<div className="rounded bg-white px-2 text-sm shadow-sm">
|
||||
{voteByType["ifNeedBe"]}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<VoteIcon type="no" />
|
||||
<div>{t("no")}</div>
|
||||
<div className="rounded bg-white px-2 text-sm shadow-sm">
|
||||
{voteByType["no"]}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx("flex flex-wrap gap-1.5 rounded border p-1.5", className)}
|
||||
>
|
||||
{voteTypes.map((voteType) => {
|
||||
const votes = voteByType[voteType];
|
||||
const count = votes.length;
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={voteType}
|
||||
className="flex h-8 select-none divide-x rounded border bg-gray-50 text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2 pl-2 pr-3">
|
||||
<VoteIcon type={voteType} />
|
||||
<div>{t(voteType)}</div>
|
||||
</div>
|
||||
<div className="flex h-full items-center justify-center px-2 text-sm font-semibold text-slate-800">
|
||||
{voteByType[voteType].length}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewParticipantModal = (props: NewParticipantModalProps) => {
|
||||
const { t } = useTranslation("app");
|
||||
const { register, formState, handleSubmit } =
|
||||
const { register, formState, setFocus, handleSubmit } =
|
||||
useForm<NewParticipantFormData>();
|
||||
const { requiredString, validEmail } = useFormValidation();
|
||||
const { poll } = usePoll();
|
||||
const addParticipant = useAddParticipantMutation();
|
||||
useMount(() => {
|
||||
setFocus("name");
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="max-w-full p-4">
|
||||
<div className="text-lg font-semibold text-slate-800">
|
||||
|
@ -96,7 +105,6 @@ export const NewParticipantModal = (props: NewParticipantModalProps) => {
|
|||
</label>
|
||||
<TextInput
|
||||
className="w-full"
|
||||
autoFocus={true}
|
||||
error={!!formState.errors.name}
|
||||
disabled={formState.isSubmitting}
|
||||
placeholder={t("namePlaceholder")}
|
||||
|
@ -132,10 +140,7 @@ export const NewParticipantModal = (props: NewParticipantModalProps) => {
|
|||
</fieldset>
|
||||
<fieldset>
|
||||
<label className="text-slate-500">{t("response")}</label>
|
||||
<VoteSummary
|
||||
votes={props.votes}
|
||||
className="rounded border bg-gray-50 py-2 px-3"
|
||||
/>
|
||||
<VoteSummary votes={props.votes} />
|
||||
</fieldset>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={props.onCancel}>{t("cancel")}</Button>
|
||||
|
@ -157,6 +162,8 @@ export const useNewParticipantModal = () => {
|
|||
|
||||
const showNewParticipantModal = (props: NewParticipantModalProps) => {
|
||||
return modalContext.render({
|
||||
showClose: true,
|
||||
overlayClosable: true,
|
||||
content: function Content({ close }) {
|
||||
return (
|
||||
<NewParticipantModal
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue