🛂 Turn permissions into functions and enable passenger self removal
jump to
@@ -30,7 +30,7 @@ } = usePermissions();
return ( <Box textAlign="center"> - {canJoinTravels && ( + {canJoinTravels() && ( <Box p={1} pt={2}> <Button sx={buttonStyle}@@ -48,7 +48,7 @@ )}
</Button> </Box> )} - {canAddToTravel && ( + {canAddToTravel() && ( <Box p={1} pt={2}> <Button sx={buttonStyle}
@@ -56,7 +56,7 @@ </Typography>
<Box pt={1}> <Tooltip title={t( - canEditEventOptions + canEditEventOptions() ? 'options.plus.activationOK' : 'options.plus.activationForbiden' )}@@ -64,7 +64,7 @@ >
<Box> <Button href={`${caroster_plus_payment_link}?client_reference_id=${event.uuid}&locale=${locale}`} - disabled={!canEditEventOptions} + disabled={!canEditEventOptions()} sx={{ backgroundColor: 'primary.light', color: 'primary.main',
@@ -103,7 +103,7 @@ {t('options.plus.admins')}
</Typography> <Button variant="text" - disabled={!canEditEventOptions} + disabled={!canEditEventOptions()} endIcon={<AddIcon />} onClick={toggleAddAdminDialog} >@@ -124,7 +124,7 @@ </ListItem>
{event.administrators?.map(email => ( <ListItem secondaryAction={ - canEditEventOptions && ( + canEditEventOptions() && ( <IconButton size="medium" onClick={() => deleteAdmin({email})}> <DeleteOutlineIcon /> </IconButton>
@@ -21,23 +21,25 @@ paddingRight: theme.spacing(12),
}, })); +export type PassengerButton = ({ + onClick, + disabled, +}: { + onClick: () => void; + passenger?: PassengerEntity; + disabled?: boolean; +}) => JSX.Element; + interface Props { passengers: PassengerEntity[]; - Button: ({ - onClick, - disabled, - }: { - onClick: () => void; - disabled?: boolean; - }) => JSX.Element; + Button: PassengerButton; isTravel?: boolean; onPress?: (passengerId: string) => void; onClick?: (passengerId: string) => void; } const PassengersList = (props: Props) => { - const {passengers, Button, onClick, onPress, isTravel} = - props; + const {passengers, Button, onClick, onPress, isTravel} = props; const theme = useTheme(); let list = passengers;@@ -59,6 +61,7 @@ passenger={passenger}
isTravel={isTravel} button={ <Button + passenger={passenger} onClick={() => onClick && onClick(passenger.id)} /> }
@@ -22,7 +22,7 @@ const {travel, toggleEditing} = props;
const theme = useTheme(); const {t} = useTranslation(); const { - userPermissions: {editableTravels}, + userPermissions: {canEditTravel}, } = usePermissions(); const {setFocusOnTravel, focusedTravel} = useMapStore();@@ -38,7 +38,7 @@ const mapElement = document?.getElementById('map');
mapElement?.scrollIntoView({behavior: 'smooth'}); }} > - {editableTravels.includes(travel.id) && ( + {canEditTravel(travel) && ( <IconButton size="small" color="primary"
@@ -24,7 +24,7 @@
const Travel = (props: Props) => { const {travel} = props; const { - userPermissions: {editableTravels, canJoinTravels, canAddToTravel}, + userPermissions: {canDeletePassenger, canJoinTravels, canAddToTravel}, } = usePermissions(); const {t} = useTranslation(); const theme = useTheme();@@ -63,7 +63,7 @@ )}
{!isEditing && ( <> <Header travel={travel} toggleEditing={toggleEditing} /> - {(canJoinTravels || canAddToTravel) && ( + {(canJoinTravels() || canAddToTravel()) && ( <> <Divider /> <AddPassengerButtons@@ -80,8 +80,8 @@ <PassengersList
passengers={travel.attributes.passengers.data} onClick={actions.sendPassengerToWaitingList} isTravel - Button={({onClick}: {onClick: () => void}) => - editableTravels.includes(travel.id) && ( + Button={({onClick, passenger}) => + canDeletePassenger({id: passenger.id, attributes: {...passenger.attributes, travel: {data: travel}}}) && ( <ListItemSecondaryAction> <Button color="primary" onClick={onClick} tabIndex={-1}> {t`travel.passengers.remove`}
@@ -1,5 +1,6 @@
import {useReducer, useState, useMemo, useCallback} from 'react'; import router from 'next/dist/client/router'; +import useMediaQuery from '@mui/material/useMediaQuery'; import Container from '@mui/material/Container'; import TuneIcon from '@mui/icons-material/Tune'; import Box from '@mui/material/Box';@@ -10,16 +11,15 @@ import Paper from '@mui/material/Paper';
import Divider from '@mui/material/Divider'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; +import {useTheme} from '@mui/material/styles'; import {Trans, useTranslation} from 'react-i18next'; import useToastStore from '../../stores/useToastStore'; import useEventStore from '../../stores/useEventStore'; import usePassengersActions from '../../hooks/usePassengersActions'; -import PassengersList from '../PassengersList'; import RemoveDialog from '../RemoveDialog'; import AddPassengerButtons from '../AddPassengerButtons'; import AssignButton from './AssignButton'; -import {useTheme} from '@mui/material/styles'; -import useMediaQuery from '@mui/material/useMediaQuery'; +import PassengersList, { PassengerButton } from '../PassengersList'; interface Props { canAddSelf: boolean;@@ -75,15 +75,15 @@ addToast(t('passenger.errors.cant_remove_passenger'));
} }; - const ListButton = isEditing - ? ({onClick}: {onClick: () => void}) => ( + const ListButton: PassengerButton = isEditing + ? ({onClick}) => ( <ListItemSecondaryAction> <IconButton size="small" color="primary" onClick={onClick}> <CancelOutlinedIcon /> </IconButton> </ListItemSecondaryAction> ) - : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => ( + : ({onClick, disabled}) => ( <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} /> );
@@ -1,24 +1,27 @@
+import {PassengerEntity, TravelEntity} from '../generated/graphql'; import useEventStore from '../stores/useEventStore'; import useProfile from './useProfile'; interface UserPermissions { - canEditEventOptions: boolean; - canEditEventDetails: boolean; - canEditWaitingList: boolean; - canAddTravel: boolean; - editableTravels: Array<string>; - canJoinTravels: boolean; - canAddToTravel: boolean; + canEditEventOptions: () => boolean; + canEditEventDetails: () => boolean; + canEditWaitingList: () => boolean; + canAddTravel: () => boolean; + canEditTravel: (travel: TravelEntity) => boolean; + canJoinTravels: () => boolean; + canAddToTravel: () => boolean; + canDeletePassenger: (passenger: PassengerEntity) => boolean; } const noPermissions = { - canEditEventOptions: false, - canEditEventDetails: false, - canEditWaitingList: false, - canAddTravel: false, - editableTravels: [], - canJoinTravels: false, - canAddToTravel: false, + canEditEventOptions: () => false, + canEditEventDetails: () => false, + canEditWaitingList: () => false, + canAddTravel: () => false, + canEditTravel: () => false, + canJoinTravels: () => false, + canAddToTravel: () => false, + canDeletePassenger: () => false, }; const usePermissions = (): {userPermissions: UserPermissions} => {@@ -29,31 +32,36 @@ const carosterPlus = event?.enabled_modules?.includes('caroster-plus');
const userIsAnonymous = !connected; const userIsEventCreator = event && profile?.email === event.email; const userIsEventAdmin = event?.administrators?.includes(profile?.email); - const travels = event?.travels?.data || []; const allPermissions: UserPermissions = { - canEditEventOptions: true, - canEditEventDetails: true, - canEditWaitingList: true, - canAddTravel: true, - editableTravels: travels.map(({id}) => id), - canJoinTravels: true, - canAddToTravel: true, + canEditEventOptions: () => true, + canEditEventDetails: () => true, + canEditWaitingList: () => true, + canAddTravel: () => true, + canEditTravel: () => true, + canJoinTravels: () => true, + canAddToTravel: () => true, + canDeletePassenger: () => true, }; if (carosterPlus) { if (userIsAnonymous) return {userPermissions: noPermissions}; else if (userIsEventCreator || userIsEventAdmin) - return {userPermissions: {...allPermissions, canAddToTravel: false}}; + return {userPermissions: {...allPermissions, canAddToTravel: () => false}}; else { - const editableTravelsCollection = travels.filter( - travel => travel.attributes.user?.data?.id !== userId - ); const carosterPlusPermissions: UserPermissions = { ...noPermissions, - editableTravels: editableTravelsCollection.map(({id}) => id), - canJoinTravels: true, - canAddTravel: true, + canEditTravel: travel => { + const travelCreatorId = travel.attributes.user?.data?.id || travel.attributes.user; + return travelCreatorId === userId; + }, + canJoinTravels: () => true, + canAddTravel: () => true, + canDeletePassenger: (passenger) => { + const travel = event?.travels?.data?.find(travel => travel.id === passenger.attributes.travel.data.id); + const travelCreatorId = travel?.attributes.user?.data?.id || travel?.attributes.user; + return travelCreatorId === userId; + }, }; return {userPermissions: carosterPlusPermissions}; }@@ -63,8 +71,8 @@ else
return { userPermissions: { ...allPermissions, - canEditEventOptions: userIsEventCreator, - canJoinTravels: connected, + canEditEventOptions: () => userIsEventCreator, + canJoinTravels: () => connected, }, }; };
@@ -127,7 +127,7 @@ >
<Typography variant="h4" pb={2}> {t('event.details')} </Typography> - {canEditEventDetails && modifyButton} + {canEditEventDetails() && modifyButton} <Box pt={2} pr={1.5}> <Typography variant="overline">{t('event.fields.name')}</Typography> <Typography variant="body1">
@@ -49,7 +49,7 @@
return ( <Box> <TravelColumns toggle={addTravelClickHandler} /> - {canAddTravel && ( + {canAddTravel() && ( <Fab onClick={addTravelClickHandler} aria-label="add-car"> {t('travel.creation.title')} </Fab>