Adjust permissions for unclaimed participants

This commit is contained in:
Luke Vella 2022-05-09 08:43:40 +01:00
parent 5c991d7011
commit ccc2896b2d
3 changed files with 31 additions and 24 deletions

View file

@ -13,7 +13,6 @@ import {
CreateCommentPayload, CreateCommentPayload,
} from "../../api-client/create-comment"; } from "../../api-client/create-comment";
import { requiredString } from "../../utils/form-validation"; import { requiredString } from "../../utils/form-validation";
import Badge from "../badge";
import Button from "../button"; import Button from "../button";
import CompactButton from "../compact-button"; import CompactButton from "../compact-button";
import Dropdown, { DropdownItem } from "../dropdown"; import Dropdown, { DropdownItem } from "../dropdown";
@ -24,7 +23,7 @@ import TruncatedLinkify from "../poll/truncated-linkify";
import UserAvatar from "../poll/user-avatar"; import UserAvatar from "../poll/user-avatar";
import { usePoll } from "../poll-context"; import { usePoll } from "../poll-context";
import { usePreferences } from "../preferences/use-preferences"; import { usePreferences } from "../preferences/use-preferences";
import { useSession } from "../session"; import { isUnclaimed, useSession } from "../session";
export interface DiscussionProps { export interface DiscussionProps {
pollId: string; pollId: string;
@ -125,7 +124,9 @@ const Discussion: React.VoidFunctionComponent<DiscussionProps> = ({
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{comments.map((comment) => { {comments.map((comment) => {
const canDelete = const canDelete =
poll.role === "admin" || session.ownsObject(comment); poll.role === "admin" ||
session.ownsObject(comment) ||
isUnclaimed(comment);
return ( return (
<motion.div <motion.div

View file

@ -15,11 +15,10 @@ import Trash from "@/components/icons/trash.svg";
import { usePoll } from "@/components/poll-context"; import { usePoll } from "@/components/poll-context";
import { requiredString } from "../../utils/form-validation"; import { requiredString } from "../../utils/form-validation";
import Badge from "../badge";
import Button from "../button"; import Button from "../button";
import { styleMenuItem } from "../menu-styles"; import { styleMenuItem } from "../menu-styles";
import NameInput from "../name-input"; import NameInput from "../name-input";
import { useSession } from "../session"; import { isUnclaimed, useSession } from "../session";
import TimeZonePicker from "../time-zone-picker"; import TimeZonePicker from "../time-zone-picker";
import PollOptions from "./mobile-poll/poll-options"; import PollOptions from "./mobile-poll/poll-options";
import TimeSlotOptions from "./mobile-poll/time-slot-options"; import TimeSlotOptions from "./mobile-poll/time-slot-options";
@ -66,7 +65,7 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
? participantById[selectedParticipantId] ? participantById[selectedParticipantId]
: undefined; : undefined;
const [editable, setEditable] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [shouldShowSaveButton, setShouldShowSaveButton] = React.useState(false); const [shouldShowSaveButton, setShouldShowSaveButton] = React.useState(false);
const formRef = React.useRef<HTMLFormElement>(null); const formRef = React.useRef<HTMLFormElement>(null);
@ -129,7 +128,7 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
{ {
onSuccess: () => { onSuccess: () => {
resolve(data); resolve(data);
setEditable(false); setIsEditing(false);
}, },
onError: reject, onError: reject,
}, },
@ -139,7 +138,7 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
onSuccess: (newParticipant) => { onSuccess: (newParticipant) => {
setSelectedParticipantId(newParticipant.id); setSelectedParticipantId(newParticipant.id);
resolve(data); resolve(data);
setEditable(false); setIsEditing(false);
}, },
onError: reject, onError: reject,
}); });
@ -152,13 +151,13 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
<Listbox <Listbox
value={selectedParticipantId} value={selectedParticipantId}
onChange={setSelectedParticipantId} onChange={setSelectedParticipantId}
disabled={editable} disabled={isEditing}
> >
<div className="menu min-w-0 grow"> <div className="menu min-w-0 grow">
<Listbox.Button <Listbox.Button
as={Button} as={Button}
className="w-full" className="w-full"
disabled={!editable} disabled={!isEditing}
data-testid="participant-selector" data-testid="participant-selector"
> >
<div className="min-w-0 grow text-left"> <div className="min-w-0 grow text-left">
@ -206,14 +205,16 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
</Listbox.Options> </Listbox.Options>
</div> </div>
</Listbox> </Listbox>
{!poll.closed && !editable ? ( {!poll.closed && !isEditing ? (
selectedParticipant && selectedParticipant &&
(role === "admin" || session.ownsObject(selectedParticipant)) ? ( (role === "admin" ||
session.ownsObject(selectedParticipant) ||
isUnclaimed(selectedParticipant)) ? (
<div className="flex space-x-3"> <div className="flex space-x-3">
<Button <Button
icon={<Pencil />} icon={<Pencil />}
onClick={() => { onClick={() => {
setEditable(true); setIsEditing(true);
reset({ reset({
name: selectedParticipant.name, name: selectedParticipant.name,
votes: selectedParticipant.votes.map( votes: selectedParticipant.votes.map(
@ -241,17 +242,17 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
icon={<PlusCircle />} icon={<PlusCircle />}
onClick={() => { onClick={() => {
reset({ name: "", votes: [] }); reset({ name: "", votes: [] });
setEditable(true); setIsEditing(true);
}} }}
> >
New New
</Button> </Button>
) )
) : null} ) : null}
{editable ? ( {isEditing ? (
<Button <Button
onClick={() => { onClick={() => {
setEditable(false); setIsEditing(false);
reset(); reset();
}} }}
> >
@ -273,7 +274,7 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
<PollOptions <PollOptions
selectedParticipantId={selectedParticipantId} selectedParticipantId={selectedParticipantId}
options={pollContext.options} options={pollContext.options}
editable={editable} editable={isEditing}
/> />
); );
case "timeSlot": case "timeSlot":
@ -281,13 +282,13 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
<TimeSlotOptions <TimeSlotOptions
selectedParticipantId={selectedParticipantId} selectedParticipantId={selectedParticipantId}
options={pollContext.options} options={pollContext.options}
editable={editable} editable={isEditing}
/> />
); );
} }
})()} })()}
<AnimatePresence> <AnimatePresence>
{shouldShowSaveButton && editable ? ( {shouldShowSaveButton && isEditing ? (
<motion.button <motion.button
type="button" type="button"
variants={{ variants={{
@ -309,7 +310,7 @@ const MobilePoll: React.VoidFunctionComponent<PollProps> = ({ pollId }) => {
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
<AnimatePresence> <AnimatePresence>
{editable ? ( {isEditing ? (
<motion.div <motion.div
variants={{ variants={{
hidden: { opacity: 0, y: -100, height: 0 }, hidden: { opacity: 0, y: -100, height: 0 },

View file

@ -11,14 +11,16 @@ export type SessionProps = {
user: UserSessionData | null; user: UserSessionData | null;
}; };
type ParticipantOrComment = {
userId: string | null;
guestId: string | null;
};
type SessionContextValue = { type SessionContextValue = {
logout: () => Promise<void>; logout: () => Promise<void>;
user: (UserSessionData & { shortName: string }) | null; user: (UserSessionData & { shortName: string }) | null;
refresh: () => void; refresh: () => void;
ownsObject: (obj: { ownsObject: (obj: ParticipantOrComment) => boolean;
userId: string | null;
guestId: string | null;
}) => boolean;
isLoading: boolean; isLoading: boolean;
}; };
@ -101,3 +103,6 @@ export const withSession = <P extends SessionProps>(
ComposedComponent.displayName = component.displayName; ComposedComponent.displayName = component.displayName;
return ComposedComponent; return ComposedComponent;
}; };
export const isUnclaimed = (obj: ParticipantOrComment) =>
!obj.guestId && !obj.userId;