feat: ✨ Caroster plus is read only to anonymous users
jump to
@@ -2,10 +2,10 @@ import {useTheme} from '@mui/material/styles';
import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import {useTranslation} from 'react-i18next'; +import usePermissions from '../../hooks/usePermissions'; interface Props { getOnClickFunction: (addSelf: boolean) => () => void; - canAddSelf: boolean; registered: boolean; variant: 'waitingList' | 'travel'; disabled?: boolean;@@ -18,13 +18,15 @@ };
const AddPassengerButtons = ({ getOnClickFunction, - canAddSelf, registered, variant, disabled, }: Props) => { const theme = useTheme(); const {t} = useTranslation(); + const { + userPermissions: {canJoinTravels, canAddToTravel}, + } = usePermissions(); const containerSx = {padding: theme.spacing(1), textAlign: 'center'}; const textSx = {@@ -36,7 +38,7 @@ };
return ( <Box sx={containerSx}> - {canAddSelf && ( + {canJoinTravels && ( <Box sx={containerSx}> <Button sx={textSx}@@ -44,7 +46,7 @@ variant="contained"
color="primary" fullWidth onClick={getOnClickFunction(true)} - disabled={registered} + disabled={disabled || registered} > {t( registered@@ -54,18 +56,20 @@ )}
</Button> </Box> )} - <Box sx={containerSx}> - <Button - sx={textSx} - variant="outlined" - color="primary" - fullWidth - onClick={getOnClickFunction(false)} - disabled={disabled} - > - {t(ADD_TO_LOCALE[variant])} - </Button> - </Box> + {canAddToTravel && ( + <Box sx={containerSx}> + <Button + sx={textSx} + variant="outlined" + color="primary" + fullWidth + onClick={getOnClickFunction(false)} + disabled={disabled} + > + {t(ADD_TO_LOCALE[variant])} + </Button> + </Box> + )} </Box> ); };
@@ -11,11 +11,10 @@ import ListItemText from '@mui/material/ListItemText';
import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'; import {useTranslation} from 'react-i18next'; -import CarosterPlusSettings from '../CarosterPlusSettings'; +import Markdown from '../../components/Markdown'; import useLocale from '../../hooks/useLocale'; -import useProfile from '../../hooks/useProfile'; +import usePermissions from '../../hooks/usePermissions'; import {Event as EventType, Module} from '../../generated/graphql'; -import Markdown from '../../components/Markdown'; interface Props { event: EventType;@@ -24,10 +23,10 @@ }
const CarosterPlusOption = ({event, modulesSettings}: Props) => { const {t} = useTranslation(); - const {profile} = useProfile(); + const { + userPermissions: {canEditEventOptions}, + } = usePermissions(); const {locale} = useLocale(); - - const userIsCreator = profile?.email === event.email; const { caroster_plus_name,@@ -57,7 +56,7 @@ </Typography>
<Box pt={1}> <Tooltip title={t( - userIsCreator + canEditEventOptions ? 'options.plus.activationOK' : 'options.plus.activationForbiden' )}@@ -65,7 +64,7 @@ >
<Box> <Button href={`${caroster_plus_payment_link}?client_reference_id=${event.uuid}&locale=${locale}`} - disabled={!userIsCreator} + disabled={!canEditEventOptions} sx={{ backgroundColor: 'primary.light', color: 'primary.main',
@@ -11,6 +11,7 @@ import AddIcon from '@mui/icons-material/Add';
import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'; import Typography from '@mui/material/Typography'; import {useTranslation} from 'react-i18next'; +import usePermissions from '../../hooks/usePermissions'; import {Event as EventType} from '../../generated/graphql'; interface Props {@@ -19,6 +20,9 @@ }
const CarosterPlusSettings = ({event}: Props) => { const {t} = useTranslation(); + const { + userPermissions: {canEditEventOptions}, + } = usePermissions(); return ( <Card@@ -45,7 +49,11 @@ <Box p={2} display="flex" justifyContent="space-between" width="100%">
<Typography pt={1} variant="body2" color="GrayText"> {t('options.plus.admins')} </Typography> - <Button variant="text" endIcon={<AddIcon />}> + <Button + variant="text" + disabled={!canEditEventOptions} + endIcon={<AddIcon />} + > {t('generic.add')} </Button> </Box>
@@ -30,14 +30,13 @@ }: {
onClick: () => void; disabled?: boolean; }) => JSX.Element; - disabled?: boolean; isTravel?: boolean; onPress?: (passengerId: string) => void; onClick?: (passengerId: string) => void; } const PassengersList = (props: Props) => { - const {passengers, Button, onClick, onPress, disabled, isTravel} = + const {passengers, Button, onClick, onPress, isTravel} = props; const theme = useTheme();@@ -51,7 +50,6 @@ list.map((passenger, index) => (
<ListItem sx={{paddingRight: theme.spacing(12)}} key={index} - disabled={disabled} button={!!onPress} onClick={() => !!onPress && onPress(passenger.id)} >@@ -62,7 +60,6 @@ isTravel={isTravel}
button={ <Button onClick={() => onClick && onClick(passenger.id)} - disabled={disabled} /> } />
@@ -10,6 +10,7 @@ import {useTranslation} from 'react-i18next';
import getMapsLink from '../../lib/getMapsLink'; import useMapStore from '../../stores/useMapStore'; import {Travel} from '../../generated/graphql'; +import usePermissions from '../../hooks/usePermissions'; interface Props { travel: Travel & {id: string};@@ -20,6 +21,9 @@ const Header = (props: Props) => {
const {travel, toggleEditing} = props; const theme = useTheme(); const {t} = useTranslation(); + const { + userPermissions: {editableTravels}, + } = usePermissions(); const {setFocusOnTravel, focusedTravel} = useMapStore(); const passengersCount = travel?.passengers?.data.length || 0;@@ -34,18 +38,25 @@ const mapElement = document?.getElementById('map');
mapElement?.scrollIntoView({behavior: 'smooth'}); }} > - <IconButton - size="small" - color="primary" - sx={{position: 'absolute', top: 0, right: 0, margin: theme.spacing(1)}} - onClick={e => { - e.stopPropagation(); - toggleEditing(); - }} - id="EditTravelBtn" - > - <TuneIcon /> - </IconButton> + {editableTravels.includes(travel.id) && ( + <IconButton + size="small" + color="primary" + sx={{ + position: 'absolute', + top: 0, + right: 0, + margin: theme.spacing(1), + }} + onClick={e => { + e.stopPropagation(); + toggleEditing(); + }} + id="EditTravelBtn" + > + <TuneIcon /> + </IconButton> + )} {!!travel.departure && ( <Typography variant="overline"
@@ -11,6 +11,7 @@ import useActions from './useActions';
import PassengersList from '../PassengersList'; import AddPassengerButtons from '../AddPassengerButtons'; import useProfile from '../../hooks/useProfile'; +import usePermissions from '../../hooks/usePermissions'; import useMapStore from '../../stores/useMapStore'; import {Travel as TravelType} from '../../generated/graphql';@@ -21,7 +22,9 @@ }
const Travel = (props: Props) => { const {travel} = props; - + const { + userPermissions: {editableTravels, canJoinTravels, canAddToTravel}, + } = usePermissions(); const {t} = useTranslation(); const theme = useTheme(); const [isEditing, toggleEditing] = useReducer(i => !i, false);@@ -49,7 +52,7 @@ position: 'relative',
boxShadow: focused ? `0px 0px 5px 2px ${theme.palette.primary.main}` : 'none', - scrollMarginTop: theme.spacing(2) + scrollMarginTop: theme.spacing(2), }} id={travel.id} >@@ -60,30 +63,31 @@ <Header travel={travel} toggleEditing={toggleEditing} />
)} {!isEditing && ( <> - <Divider /> - <AddPassengerButtons - getOnClickFunction={props.getAddPassengerFunction} - canAddSelf={connected} - registered={registered} - variant="travel" - disabled={disableNewPassengers} - /> + {(canJoinTravels || canAddToTravel) && ( + <> + <Divider /> + <AddPassengerButtons + getOnClickFunction={props.getAddPassengerFunction} + registered={registered} + variant="travel" + disabled={disableNewPassengers} + /> + </> + )} {travel.passengers.data.length > 0 && <Divider />} <PassengersList passengers={travel.passengers.data} onClick={actions.sendPassengerToWaitingList} isTravel - Button={({onClick}: {onClick: () => void}) => ( - <ListItemSecondaryAction> - <Button - color="primary" - onClick={onClick} - tabIndex={-1} - > - {t`travel.passengers.remove`} - </Button> - </ListItemSecondaryAction> - )} + Button={({onClick}: {onClick: () => void}) => + editableTravels.includes(travel.id) && ( + <ListItemSecondaryAction> + <Button color="primary" onClick={onClick} tabIndex={-1}> + {t`travel.passengers.remove`} + </Button> + </ListItemSecondaryAction> + ) + } /> </> )}
@@ -0,0 +1,74 @@
+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; +} + +const noPermissions = { + canEditEventOptions: false, + canEditEventDetails: false, + canEditWaitingList: false, + canAddTravel: false, + editableTravels: [], + canJoinTravels: false, + canAddToTravel: false, +}; + +const usePermissions = (): {userPermissions: UserPermissions} => { + const {event} = useEventStore(); + const {profile, connected, userId} = useProfile(); + + const carosterPlus = event?.enabled_modules?.includes('caroster-plus'); + + const userIsAnonymous = !connected; + const userIsEventCreator = event && profile?.email === event.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, + }; + + if (carosterPlus) { + if (userIsAnonymous) return {userPermissions: noPermissions}; + if (userIsEventCreator) + return {userPermissions: {...allPermissions, canAddToTravel: false}}; + + const editableTravelsCollection = travels.filter(({attributes}) => { + const isDriver = attributes.user?.data?.id === userId; + return !isDriver; + }); + + const carosterPlusPermissions: UserPermissions = { + ...noPermissions, + editableTravels: editableTravelsCollection.map(({id}) => id), + canJoinTravels: true, + }; + + return {userPermissions: carosterPlusPermissions}; + } + + // Caroster Vanilla permissions + return { + userPermissions: { + ...allPermissions, + canEditEventOptions: userIsEventCreator, + canJoinTravels: connected, + }, + }; +}; + +export default usePermissions;
@@ -18,6 +18,8 @@ import {PropsWithChildren, useState} from 'react';
import {useTranslation} from 'react-i18next'; import pageUtils from '../../../lib/pageUtils'; import ShareEvent from '../../../containers/ShareEvent'; +import PlaceInput from '../../../containers/PlaceInput'; +import usePermissions from '../../../hooks/usePermissions'; import useEventStore from '../../../stores/useEventStore'; import useToastStore from '../../../stores/useToastStore'; import EventLayout, {TabComponent} from '../../../layouts/Event';@@ -25,7 +27,6 @@ import {
EventByUuidDocument, useUpdateEventMutation, } from '../../../generated/graphql'; -import PlaceInput from '../../../containers/PlaceInput'; interface Props { eventUUID: string;@@ -38,6 +39,9 @@ };
const DetailsTab: TabComponent = ({}) => { const {t} = useTranslation(); + const { + userPermissions: {canEditEventDetails}, + } = usePermissions(); const theme = useTheme(); const [updateEvent] = useUpdateEventMutation(); const addToast = useToastStore(s => s.addToast);@@ -123,7 +127,7 @@ >
<Typography variant="h4" pb={2}> {t('event.details')} </Typography> - {modifyButton} + {canEditEventDetails && modifyButton} <Box pt={2} pr={1.5}> <Typography variant="overline">{t('event.fields.name')}</Typography> <Typography variant="body1">
@@ -7,6 +7,7 @@ import NewTravelDialog from '../../../containers/NewTravelDialog';
import VehicleChoiceDialog from '../../../containers/VehicleChoiceDialog'; import Fab from '../../../containers/Fab'; import pageUtils from '../../../lib/pageUtils'; +import usePermissions from '../../../hooks/usePermissions'; import EventLayout, {TabComponent} from '../../../layouts/Event'; import { EventByUuidDocument,@@ -26,6 +27,7 @@
const TravelsTab: TabComponent = () => { const {t} = useTranslation(); const session = useSession(); + const {userPermissions: {canAddTravel}} = usePermissions() const [openNewTravelContext, toggleNewTravel] = useState({opened: false}); const [openVehicleChoice, toggleVehicleChoice] = useReducer(i => !i, false);@@ -46,6 +48,7 @@ <TravelColumns toggle={addTravelClickHandler} />
<Fab onClick={addTravelClickHandler} aria-label="add-car" + disabled={!canAddTravel} > {t('travel.creation.title')} </Fab>