all repos — caroster @ 69a8f789f50a85eb1d8519dd7d3c54eba39233c3

[Octree] Group carpool to your event https://caroster.io

🛂 Turn permissions into functions and enable passenger self removal
Simon Mulquin simon@octree.ch
Tue, 13 Feb 2024 19:57:18 +0000
commit

69a8f789f50a85eb1d8519dd7d3c54eba39233c3

parent

6a778a00a5bb7e86d12260b5efd5421a375e7382

M frontend/containers/AddPassengerButtons/index.tsxfrontend/containers/AddPassengerButtons/index.tsx

@@ -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}
M frontend/containers/CarosterPlusOption/index.tsxfrontend/containers/CarosterPlusOption/index.tsx

@@ -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',
M frontend/containers/CarosterPlusSettings/index.tsxfrontend/containers/CarosterPlusSettings/index.tsx

@@ -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>
M frontend/containers/PassengersList/index.tsxfrontend/containers/PassengersList/index.tsx

@@ -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)} /> }
M frontend/containers/Travel/Header.tsxfrontend/containers/Travel/Header.tsx

@@ -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"
M frontend/containers/Travel/index.tsxfrontend/containers/Travel/index.tsx

@@ -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`}
M frontend/containers/WaitingList/index.tsxfrontend/containers/WaitingList/index.tsx

@@ -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} /> );
M frontend/hooks/usePermissions.tsfrontend/hooks/usePermissions.ts

@@ -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, }, }; };
M frontend/pages/e/[uuid]/details.tsxfrontend/pages/e/[uuid]/details.tsx

@@ -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">
M frontend/pages/e/[uuid]/index.tsxfrontend/pages/e/[uuid]/index.tsx

@@ -49,7 +49,7 @@

return ( <Box> <TravelColumns toggle={addTravelClickHandler} /> - {canAddTravel && ( + {canAddTravel() && ( <Fab onClick={addTravelClickHandler} aria-label="add-car"> {t('travel.creation.title')} </Fab>