all repos — caroster @ 8cf97f406bef2a68157869e2f6398d45b8bec77a

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

feat: ✨ Caroster plus is read only to anonymous users
Simon Mulquin simon@octree.ch
Mon, 05 Feb 2024 08:20:13 +0000
commit

8cf97f406bef2a68157869e2f6398d45b8bec77a

parent

d33867f4372a6c5c196e4ec4d90a10476d6d4e3b

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

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

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

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

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

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

@@ -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> + ) + } /> </> )}
A frontend/hooks/usePermissions.ts

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

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

@@ -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>