all repos — caroster @ d48304caf9bbcf05225577509e9eff78e5a0df95

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

feat: :sparkles: Admin can manage waiting list for Plus event

#553
Tim Izzo tim@octree.ch
Wed, 04 Dec 2024 17:07:40 +0100
commit

d48304caf9bbcf05225577509e9eff78e5a0df95

parent

d3a5bc4cf9c4e11655e644f7ea0489791ac75d06

M backend/src/api/email/locales/en.jsonbackend/src/api/email/locales/en.json

@@ -32,6 +32,10 @@ "NewPassengerInYourTrip": {

"title": "<%= event.name %> - A passenger has been added to your trip", "content": "A passenger has been added to your trip on [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)" }, + "AssignedByAdmin": { + "title": "<%= event.name %> - You have been added to a trip", + "content": "An administrator of [<%= event.name %>](<%= host %>/e/<%= event.uuid %>) has added you to trip '<%= travel.vehicleName %>'." + }, "AddedAsAdmin": { "title": "You have been promoted as administrator to an event", "content": "You have been promoted as administrator to [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)."
M backend/src/api/email/locales/fr.jsonbackend/src/api/email/locales/fr.json

@@ -32,6 +32,10 @@ "NewPassengerInYourTrip": {

"title": "<%= event.name %> - Un passager a été ajouté à votre trajet", "content": "Un passager a été ajouté à votre trajet pour [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)." }, + "AssignedByAdmin": { + "title": "<%= event.name %> - Vous avez été assigné à un trajet", + "content": "Un administrateur de l'événement [<%= event.name %>](<%= host %>/e/<%= event.uuid %>) vous a assigné au trajet '<%= travel.vehicleName %>'." + }, "AddedAsAdmin": { "title": "Vous avez été promu administrateur d'un événement", "content": "Vous avez été promu administrateur sur le Caroster [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)."
M backend/src/api/email/locales/it.jsonbackend/src/api/email/locales/it.json

@@ -32,6 +32,10 @@ "NewPassengerInYourTrip": {

"content": "Un passeggero è stato aggiunto al tuo passaggio per l'evento [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)", "title": "<%= event.name %> - Un passeggero è stato aggiunto al tuo passaggio" }, + "AssignedByAdmin": { + "title": "<%= event.name %> - You have been added to a trip", + "content": "An administrator of [<%= event.name %>](<%= host %>/e/<%= event.uuid %>) has added you to trip '<%= travel.vehicleName %>'." + }, "AddedAsAdmin": { "title": "Sei stato/a promosso/a come amministratore di un evento", "content": "Sei stato/a promosso/a amministratore dell'evento [<%= event.name %>](<%= host %>/e/<%= event.uuid %>)."
M backend/src/api/email/locales/nl.jsonbackend/src/api/email/locales/nl.json

@@ -24,6 +24,10 @@ "NewPassengerInYourTrip": {

"title": "<%= event.name %> - Passagier toegevoegd aan reis", "content": "Er is een passagier toegevoegd aan ‘[<%= event.name %>](<%= host %>/e/<%= event.uuid %>)’" }, + "AssignedByAdmin": { + "title": "<%= event.name %> - You have been added to a trip", + "content": "An administrator of [<%= event.name %>](<%= host %>/e/<%= event.uuid %>) has added you to trip '<%= travel.vehicleName %>'." + }, "AddedAsAdmin": { "title": "U bent toegevoegd als beheerder van een afspraak", "content": "U bent toegevoegd als beheerder van ‘[<%= event.name %>](<%= host %>/e/<%= event.uuid %>)’."
M backend/src/api/event/services/event.tsbackend/src/api/event/services/event.ts

@@ -17,6 +17,39 @@ },

populate: ["travel", "user"], }); }, + getTripAlerts: async (event) => { + const tripAlerts = await strapi.entityService.findMany( + "api::trip-alert.trip-alert", + { + filters: { + event: { id: event.id }, + user: { $not: null }, + enabled: true, + }, + populate: ["user"], + } + ); + const eventPassengers = await strapi.entityService.findMany( + "api::passenger.passenger", + { + filters: { + event: { id: event.id }, + travel: { + id: { + $null: false, + }, + }, + }, + populate: ["user"], + } + ); + const usersInTravel = eventPassengers + .map((passenger) => passenger.user?.id) + .filter(Boolean); + return tripAlerts.filter( + (tripAlert) => !usersInTravel.includes(tripAlert.user.id) + ); + }, sendDailyRecap: async (event) => { const referenceDate = DateTime.now().minus({ day: 1 }); const hasBeenModified =
M backend/src/api/notification/content-types/notification/schema.jsonbackend/src/api/notification/content-types/notification/schema.json

@@ -21,6 +21,7 @@ "NewTripAlert",

"DeletedTrip", "DeletedYourTrip", "DeletedFromTrip", + "AssignedByAdmin", "AddedAsAdmin", "EventCreated", "EventEnded",
M backend/src/api/passenger/content-types/passenger/lifecycles.tsbackend/src/api/passenger/content-types/passenger/lifecycles.ts

@@ -1,5 +1,8 @@

export default { - async afterCreate({ params }) { + async beforeCreate(event) { + event.state.isAdmin = event.params.data.isAdmin; + }, + async afterCreate({ params, state }) { if (params.data.travel) { const travel = await strapi.entityService.findOne( "api::travel.travel",

@@ -14,6 +17,17 @@ data: {

type: "NewPassengerInYourTrip", event: params.data.event, user: travel.user?.id, + }, + }); + + if (travel && state.isAdmin) + strapi.entityService.create("api::notification.notification", { + data: { + type: "AssignedByAdmin", + event: params.data.event, + user: params.data.user, + // @ts-expect-error + payload: { travel }, }, }); }
M backend/src/api/passenger/policies/add-only-self.tsbackend/src/api/passenger/policies/add-only-self.ts

@@ -5,9 +5,19 @@ const user = policyContext.state.user;

const inputUserId = policyContext.args?.data?.user; if (inputUserId) { - if (user && `${user.id}` !== inputUserId) - throw new errors.UnauthorizedError("Can't add another linked user"); - else if (!user) + if (user && `${user.id}` !== inputUserId) { + const event = await strapi.entityService.findOne( + "api::event.event", + policyContext.args.data.event + ); + const administrators = event.administrators?.split(/, ?/) || []; + const isEventAdmin = [...administrators, event.email].includes( + user.email + ); + if (!isEventAdmin) + throw new errors.UnauthorizedError("Can't add another linked user"); + else policyContext.args.data.isAdmin = true; + } else if (!user) throw new errors.UnauthorizedError("Can't add linked user as anonymous"); } };
M backend/src/api/passenger/policies/check-creation.tsbackend/src/api/passenger/policies/check-creation.ts

@@ -9,7 +9,11 @@ const event = await strapi.entityService.findOne("api::event.event", eventId);

if (!event) throw new errors.NotFoundError(`Event not found`); if (event.enabled_modules?.includes("caroster-plus")) { - if (user) policyContext.args.data.user = user.id; - else throw new errors.ForbiddenError(); + const administrators = event.administrators?.split(/, ?/) || []; + const isEventAdmin = [...administrators, event.email].includes(user.email); + if (!isEventAdmin) { + if (user) policyContext.args.data.user = user.id; + else throw new errors.ForbiddenError(); + } } };
M backend/src/graphql/event/event.tsbackend/src/graphql/event/event.ts

@@ -6,6 +6,9 @@ definition(t) {

t.field("waitingPassengers", { type: "PassengerRelationResponseCollection", }); + t.field("tripAlerts", { + type: "TripAlertEntityResponseCollection", + }); }, }), nexus.extendType({

@@ -44,6 +47,18 @@ .service("format").returnTypes;

return toEntityResponseCollection(waitingPassengers, { args, resourceUID: "api::passenger.passenger", + }); + }, + tripAlerts: async (root, args) => { + const tripAlerts = await strapi + .service("api::event.event") + .getTripAlerts(root); + const { toEntityResponseCollection } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponseCollection(tripAlerts, { + args, + resourceUID: "api::trip-alert.trip-alert", }); }, },

@@ -124,6 +139,9 @@ auth: false,

}, "Event.waitingPassengers": { auth: false, + }, + "Event.tripAlerts": { + auth: true, }, "Event.travels": { auth: false,
M backend/src/graphql/trip-alert/index.tsbackend/src/graphql/trip-alert/index.ts

@@ -107,6 +107,9 @@ },

"Mutation.setTripAlert": { auth: true, }, + "TripAlert.user": { + auth: true, + }, }, }), ];
M backend/types/generated/contentTypes.d.tsbackend/types/generated/contentTypes.d.ts

@@ -548,6 +548,7 @@ 'NewTripAlert',

'DeletedTrip', 'DeletedYourTrip', 'DeletedFromTrip', + 'AssignedByAdmin', 'AddedAsAdmin', 'EventCreated', 'EventEnded',
M frontend/containers/DrawerMenu/index.tsxfrontend/containers/DrawerMenu/index.tsx

@@ -7,6 +7,7 @@ import {useRouter} from 'next/router';

import useProfile from '../../hooks/useProfile'; import DrawerMenuItem from './DrawerMenuItem'; import useEventStore from '../../stores/useEventStore'; +import usePermissions from '../../hooks/usePermissions'; interface Props { eventUuid: string;

@@ -20,6 +21,10 @@ const event = useEventStore(s => s.event);

const {connected} = useProfile(); const appLink = connected ? '/dashboard' : `/e/${eventUuid}` || ''; const isCarosterPlusEvent = event?.enabled_modules?.includes('caroster-plus'); + + const { + userPermissions: {canSeeAdminWaitingList}, + } = usePermissions(); const router = useRouter(); const {

@@ -109,6 +114,17 @@ active={

router.pathname === `/e/[uuid]/waitingList` || router.pathname === `/e/[uuid]/assign/[passengerId]` } + /> + )} + + {isCarosterPlusEvent && canSeeAdminWaitingList() && ( + <DrawerMenuItem + title={t('drawer.waitingList')} + onClick={() => + router.push(`/e/${uuid}/adminWaitingList`, null, {shallow: true}) + } + icon="group" + active={router.pathname === `/e/[uuid]/adminWaitingList`} /> )}
A frontend/containers/TripAlertsList/ListHeader.tsx

@@ -0,0 +1,42 @@

+import {Box, Divider, Typography} from '@mui/material'; +import {useMemo} from 'react'; +import {useTranslation} from 'react-i18next'; +import useEventStore from '../../stores/useEventStore'; + +type Props = {}; + +const ListHeader = (props: Props) => { + const {t} = useTranslation(); + const event = useEventStore(s => s.event); + const travels = useMemo( + () => + event?.travels?.data?.length > 0 ? event?.travels?.data.slice() : [], + [event?.travels?.data] + ); + + const count = useMemo(() => { + if (!travels) return; + return travels.reduce((count, {attributes: {seats, passengers}}) => { + if (!seats) return 0; + else if (!passengers) return count + seats; + return count + seats - passengers?.data?.length; + }, 0); + }, [travels]); + + return ( + <> + <Box p={2}> + <Typography variant="h5">{t('passenger.title')}</Typography> + <Typography variant="overline"> + {t('passenger.availability.seats', {count})} + </Typography> + <Typography variant="body2" mt={2} whiteSpace="pre-line"> + {t`passenger.waitingList.helper`} + </Typography> + </Box> + <Divider /> + </> + ); +}; + +export default ListHeader;
A frontend/containers/TripAlertsList/TravelSelectionModal.tsx

@@ -0,0 +1,46 @@

+import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + List, +} from '@mui/material'; +import {useTranslation} from 'react-i18next'; +import useEventStore from '../../stores/useEventStore'; +import AvailableTravel from '../AssignPassenger/AvailableTravel'; +import {TravelEntity} from '../../generated/graphql'; + +type Props = { + open: boolean; + onClose: () => void; + onAssign: (travel: TravelEntity) => void; +}; + +const TravelSelectionModal = (props: Props) => { + const {open, onClose, onAssign} = props; + const {t} = useTranslation(); + const travels = useEventStore(s => s.event?.travels?.data || []); + + return ( + <Dialog open={open} onClose={onClose}> + <DialogTitle>{t`passenger.assign.title`}</DialogTitle> + <DialogContent sx={{p: 0}}> + <List disablePadding> + {travels?.map(travel => ( + <AvailableTravel + key={travel.id} + travel={travel} + assign={onAssign} + /> + ))} + </List> + </DialogContent> + <DialogActions> + <Button onClick={onClose}>{t`generic.cancel`}</Button> + </DialogActions> + </Dialog> + ); +}; + +export default TravelSelectionModal;
A frontend/containers/TripAlertsList/WaitingListItem.tsx

@@ -0,0 +1,72 @@

+import { + Box, + Chip, + Icon, + IconButton, + ListItem, + ListItemText, + useTheme, +} from '@mui/material'; +import {TripAlertEntity} from '../../generated/graphql'; +import {useTranslation} from 'react-i18next'; +import RadarIcon from '@mui/icons-material/Radar'; +import useProfile from '../../hooks/useProfile'; + +type Props = { + tripAlert: TripAlertEntity; + onClick: () => void; + isLast?: boolean; +}; + +const WaitingListItem = ({tripAlert, onClick, isLast}: Props) => { + const {t} = useTranslation(); + const theme = useTheme(); + const {userId} = useProfile(); + + const user = tripAlert.attributes.user?.data.attributes; + const userName = `${user.firstName} ${user.lastName}`; + const isLoggedUser = `${userId}` === tripAlert.attributes.user?.data.id; + + return ( + <ListItem divider={!isLast}> + <ListItemText + primary={ + <> + {userName} + {isLoggedUser && ( + <Chip + sx={{marginLeft: 1}} + label={t('generic.me')} + variant="outlined" + size="small" + /> + )} + </> + } + secondary={ + <> + {tripAlert.attributes.address} + <Box display="flex" alignItems="center" gap={0.5}> + <RadarIcon fontSize="small" /> + {tripAlert.attributes.radius} km + </Box> + </> + } + /> + <IconButton + onClick={onClick} + color="primary" + sx={{ + borderRadius: 1, + padding: 0, + fontSize: theme.typography.button, + }} + > + {t('passenger.actions.place')} + <Icon>chevron_right</Icon> + </IconButton> + </ListItem> + ); +}; + +export default WaitingListItem;
A frontend/containers/TripAlertsList/index.tsx

@@ -0,0 +1,123 @@

+import { + Box, + Button, + Container, + List, + Paper, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import { + PassengerInput, + TravelEntity, + TripAlertEntity, + useCreatePassengerMutation, + useEventTripAlertsQuery, +} from '../../generated/graphql'; +import WaitingListItem from './WaitingListItem'; +import {useState} from 'react'; +import TravelSelectionModal from './TravelSelectionModal'; +import useEventStore from '../../stores/useEventStore'; +import useToastStore from '../../stores/useToastStore'; +import {useTranslation} from 'react-i18next'; +import {useRouter} from 'next/router'; +import ListHeader from './ListHeader'; + +type Props = {}; + +const TripAlertsList = (props: Props) => { + const {t} = useTranslation(); + const theme = useTheme(); + const router = useRouter(); + const mobile = useMediaQuery(theme.breakpoints.down('md')); + const [focusAlert, setFocusAlert] = useState<TripAlertEntity>(null); + const [createPassenger] = useCreatePassengerMutation(); + const event = useEventStore(s => s.event); + const addToast = useToastStore(s => s.addToast); + const clearToast = useToastStore(s => s.clearToast); + + const {data} = useEventTripAlertsQuery({ + variables: {uuid: event?.uuid}, + skip: !event?.uuid, + }); + const tripAlerts = + data?.eventByUUID?.data?.attributes?.tripAlerts?.data || []; + + const onAssign = async (travel: TravelEntity) => { + try { + const user = focusAlert.attributes.user.data; + const passenger: PassengerInput = { + email: user.attributes.email, + location: focusAlert.attributes.address, + travel: travel.id, + user: user.id, + event: event.id, + name: user.attributes.firstName + ? `${user.attributes.firstName} ${user.attributes.lastName}` + : user.attributes.email, + }; + await createPassenger({ + variables: {passenger}, + refetchQueries: ['eventTripAlerts'], + }); + setFocusAlert(null); + addToast( + t('passenger.success.added_to_car', { + name: passenger.name, + }), + <Button + size="small" + color="primary" + variant="contained" + onClick={() => { + router.push(`/e/${event.uuid}`); + clearToast(); + }} + > + {t('passenger.success.goToTravels')} + </Button> + ); + } catch (error) { + console.error(error); + addToast(t`passenger.errors.cant_select_travel`); + } + }; + + if (!tripAlerts || tripAlerts.length === 0) + return ( + <Container maxWidth="sm" sx={{mt: 11, mx: 0, px: mobile ? 2 : 4}}> + <Paper sx={{width: '480px', maxWidth: '100%'}}> + <ListHeader /> + <Box p={2} textAlign="center"> + <Typography variant="caption">{t`passenger.waitingList.empty`}</Typography> + </Box> + </Paper> + </Container> + ); + + return ( + <Container maxWidth="sm" sx={{mt: 11, mx: 0, px: mobile ? 2 : 4}}> + <Paper sx={{width: '480px', maxWidth: '100%'}}> + <ListHeader /> + <List> + {tripAlerts.map((tripAlert, idx, arr) => ( + <WaitingListItem + key={tripAlert.id} + tripAlert={tripAlert} + onClick={() => setFocusAlert(tripAlert)} + isLast={idx === arr.length - 1} + /> + ))} + </List> + </Paper> + <TravelSelectionModal + open={!!focusAlert} + onClose={() => setFocusAlert(null)} + onAssign={onAssign} + /> + </Container> + ); +}; + +export default TripAlertsList;
M frontend/generated/graphql.tsxfrontend/generated/graphql.tsx

@@ -149,6 +149,7 @@ longitude?: Maybe<Scalars['Float']['output']>;

name: Scalars['String']['output']; passengers?: Maybe<PassengerRelationResponseCollection>; travels?: Maybe<TravelRelationResponseCollection>; + tripAlerts?: Maybe<TripAlertEntityResponseCollection>; updatedAt?: Maybe<Scalars['DateTime']['output']>; uuid?: Maybe<Scalars['String']['output']>; waitingPassengers?: Maybe<PassengerRelationResponseCollection>;

@@ -1941,6 +1942,13 @@

export type EventByUuidQuery = { __typename?: 'Query', eventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, enabled_modules?: any | null, email: string, lang?: Enum_Event_Lang | null, administrators?: Array<string | null> | null, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departureDate?: any | null, departureTime?: string | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, phoneCountry?: string | null, seats?: number | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null } | null } | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, email?: string | null, phone?: string | null, phoneCountry?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; +export type EventTripAlertsQueryVariables = Exact<{ + uuid: Scalars['String']['input']; +}>; + + +export type EventTripAlertsQuery = { __typename?: 'Query', eventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', tripAlerts?: { __typename?: 'TripAlertEntityResponseCollection', data: Array<{ __typename?: 'TripAlertEntity', id?: string | null, attributes?: { __typename?: 'TripAlert', address?: string | null, radius?: number | null, latitude?: number | null, longitude?: number | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null, email: string } | null } | null } | null } | null }> } | null } | null } | null } | null }; + export type ModuleQueryVariables = Exact<{ locale: Scalars['I18NLocaleCode']['input']; }>;

@@ -2273,8 +2281,8 @@ export function useTripAlertLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TripAlertQuery, TripAlertQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<TripAlertQuery, TripAlertQueryVariables>(TripAlertDocument, options); } -export function useTripAlertSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<TripAlertQuery, TripAlertQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useTripAlertSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<TripAlertQuery, TripAlertQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<TripAlertQuery, TripAlertQueryVariables>(TripAlertDocument, options); } export type TripAlertQueryHookResult = ReturnType<typeof useTripAlertQuery>;

@@ -2628,14 +2636,79 @@ export function useEventByUuidLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EventByUuidQuery, EventByUuidQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<EventByUuidQuery, EventByUuidQueryVariables>(EventByUuidDocument, options); } -export function useEventByUuidSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<EventByUuidQuery, EventByUuidQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useEventByUuidSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<EventByUuidQuery, EventByUuidQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<EventByUuidQuery, EventByUuidQueryVariables>(EventByUuidDocument, options); } export type EventByUuidQueryHookResult = ReturnType<typeof useEventByUuidQuery>; export type EventByUuidLazyQueryHookResult = ReturnType<typeof useEventByUuidLazyQuery>; export type EventByUuidSuspenseQueryHookResult = ReturnType<typeof useEventByUuidSuspenseQuery>; export type EventByUuidQueryResult = Apollo.QueryResult<EventByUuidQuery, EventByUuidQueryVariables>; +export const EventTripAlertsDocument = gql` + query eventTripAlerts($uuid: String!) { + eventByUUID(uuid: $uuid) { + data { + id + attributes { + tripAlerts { + data { + id + attributes { + address + radius + latitude + longitude + user { + data { + id + attributes { + firstName + lastName + email + } + } + } + } + } + } + } + } + } +} + `; + +/** + * __useEventTripAlertsQuery__ + * + * To run a query within a React component, call `useEventTripAlertsQuery` and pass it any options that fit your needs. + * When your component renders, `useEventTripAlertsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useEventTripAlertsQuery({ + * variables: { + * uuid: // value for 'uuid' + * }, + * }); + */ +export function useEventTripAlertsQuery(baseOptions: Apollo.QueryHookOptions<EventTripAlertsQuery, EventTripAlertsQueryVariables> & ({ variables: EventTripAlertsQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery<EventTripAlertsQuery, EventTripAlertsQueryVariables>(EventTripAlertsDocument, options); + } +export function useEventTripAlertsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EventTripAlertsQuery, EventTripAlertsQueryVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery<EventTripAlertsQuery, EventTripAlertsQueryVariables>(EventTripAlertsDocument, options); + } +export function useEventTripAlertsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<EventTripAlertsQuery, EventTripAlertsQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery<EventTripAlertsQuery, EventTripAlertsQueryVariables>(EventTripAlertsDocument, options); + } +export type EventTripAlertsQueryHookResult = ReturnType<typeof useEventTripAlertsQuery>; +export type EventTripAlertsLazyQueryHookResult = ReturnType<typeof useEventTripAlertsLazyQuery>; +export type EventTripAlertsSuspenseQueryHookResult = ReturnType<typeof useEventTripAlertsSuspenseQuery>; +export type EventTripAlertsQueryResult = Apollo.QueryResult<EventTripAlertsQuery, EventTripAlertsQueryVariables>; export const ModuleDocument = gql` query module($locale: I18NLocaleCode!) { module(locale: $locale) {

@@ -2676,8 +2749,8 @@ export function useModuleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ModuleQuery, ModuleQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<ModuleQuery, ModuleQueryVariables>(ModuleDocument, options); } -export function useModuleSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<ModuleQuery, ModuleQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useModuleSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<ModuleQuery, ModuleQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<ModuleQuery, ModuleQueryVariables>(ModuleDocument, options); } export type ModuleQueryHookResult = ReturnType<typeof useModuleQuery>;

@@ -2732,8 +2805,8 @@ export function useUserNotificationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<UserNotificationsQuery, UserNotificationsQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<UserNotificationsQuery, UserNotificationsQueryVariables>(UserNotificationsDocument, options); } -export function useUserNotificationsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<UserNotificationsQuery, UserNotificationsQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useUserNotificationsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<UserNotificationsQuery, UserNotificationsQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<UserNotificationsQuery, UserNotificationsQueryVariables>(UserNotificationsDocument, options); } export type UserNotificationsQueryHookResult = ReturnType<typeof useUserNotificationsQuery>;

@@ -2930,8 +3003,8 @@ export function useSettingLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SettingQuery, SettingQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<SettingQuery, SettingQueryVariables>(SettingDocument, options); } -export function useSettingSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<SettingQuery, SettingQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useSettingSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<SettingQuery, SettingQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<SettingQuery, SettingQueryVariables>(SettingDocument, options); } export type SettingQueryHookResult = ReturnType<typeof useSettingQuery>;

@@ -3080,8 +3153,8 @@ export function useProfileLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProfileQuery, ProfileQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options); } -export function useProfileSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<ProfileQuery, ProfileQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useProfileSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<ProfileQuery, ProfileQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options); } export type ProfileQueryHookResult = ReturnType<typeof useProfileQuery>;

@@ -3165,8 +3238,8 @@ export function useFindUserVehiclesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindUserVehiclesQuery, FindUserVehiclesQueryVariables>) {

const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery<FindUserVehiclesQuery, FindUserVehiclesQueryVariables>(FindUserVehiclesDocument, options); } -export function useFindUserVehiclesSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<FindUserVehiclesQuery, FindUserVehiclesQueryVariables>) { - const options = {...defaultOptions, ...baseOptions} +export function useFindUserVehiclesSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindUserVehiclesQuery, FindUserVehiclesQueryVariables>) { + const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} return Apollo.useSuspenseQuery<FindUserVehiclesQuery, FindUserVehiclesQueryVariables>(FindUserVehiclesDocument, options); } export type FindUserVehiclesQueryHookResult = ReturnType<typeof useFindUserVehiclesQuery>;
M frontend/graphql/event.gqlfrontend/graphql/event.gql

@@ -122,3 +122,34 @@ ...EventFields

} } } + +query eventTripAlerts($uuid: String!) { + eventByUUID(uuid: $uuid) { + data { + id + attributes { + tripAlerts { + data { + id + attributes { + address + radius + latitude + longitude + user { + data { + id + attributes { + firstName + lastName + email + } + } + } + } + } + } + } + } + } +}
M frontend/hooks/usePermissions.tsfrontend/hooks/usePermissions.ts

@@ -6,6 +6,7 @@ interface UserPermissions {

canEditEventOptions: () => boolean; canEditEventDetails: () => boolean; canEditWaitingList: () => boolean; + canSeeAdminWaitingList: () => boolean; canSetAlert: () => boolean; canAddTravel: () => boolean; canEditTravel: (travel: TravelEntity) => boolean;

@@ -20,6 +21,7 @@ const noPermissions = {

canEditEventOptions: () => false, canEditEventDetails: () => false, canEditWaitingList: () => false, + canSeeAdminWaitingList: () => false, canSetAlert: () => false, canAddTravel: () => false, canEditTravel: () => false,

@@ -43,6 +45,7 @@ const allPermissions: UserPermissions = {

canEditEventOptions: () => true, canEditEventDetails: () => true, canEditWaitingList: () => true, + canSeeAdminWaitingList: () => true, canSetAlert: () => true, canAddTravel: () => true, canSeeTravelDetails: () => true,
M frontend/lib/pageUtils.tsfrontend/lib/pageUtils.ts

@@ -5,10 +5,12 @@ import {ProfileDocument, SettingDocument} from '../generated/graphql';

import {initializeApollo, APOLLO_STATE_PROP_NAME} from './apolloClient'; import {getCookie, hashText} from './cookies'; import nextI18NextConfig from '../next-i18next.config'; +import {Session} from 'next-auth'; type ServerSideExtension = ( context: any, - apolloClient: ApolloClient<any> + apolloClient: ApolloClient<any>, + session?: Session ) => Promise<ExtensionResult | void>; type ExtensionResult = {

@@ -57,7 +59,7 @@ });

let extensionProps = {}; if (extension) { - const extensionReturn = await extension(context, apolloClient); + const extensionReturn = await extension(context, apolloClient, session); extensionProps = extensionReturn?.props || {}; if (extensionReturn?.notFound) { return {
M frontend/locales/de.jsonfrontend/locales/de.json

@@ -18,12 +18,12 @@ "dashboard.actions.add_event": "Caroster erstellen",

"dashboard.actions.see_event": "Zu Caroster gehen", "dashboard.noEvent.create_event": "$t(menu.new_event)", "dashboard.noEvent.title": "Willkommen bei Caroster", - "dashboard.sections.future": "Kommender Caroster", - "dashboard.sections.future_plural": "Kommende Caroster", - "dashboard.sections.noDate": "Caroster ohne Datum", - "dashboard.sections.noDate_plural": "Carosters ohne Datum", - "dashboard.sections.past": "Vergangener Caroster", - "dashboard.sections.past_plural": "Vergangene Caroster", + "dashboard.sections.future_one": "Kommender Caroster", + "dashboard.sections.future_other": "Kommende Caroster", + "dashboard.sections.noDate_one": "Caroster ohne Datum", + "dashboard.sections.noDate_other": "Carosters ohne Datum", + "dashboard.sections.past_one": "Vergangener Caroster", + "dashboard.sections.past_other": "Vergangene Caroster", "dashboard.title": "$t(menu.dashboard)", "date.today": "Heute", "drawer.alerts": "Alarme",

@@ -132,11 +132,11 @@ "passenger.assign.assign": "Zuweisen",

"passenger.assign.availableCars": "Verfügbare Fahrten", "passenger.assign.departure": "Abfahrt: ", "passenger.assign.no_travel.title": "Im Moment sind keine Plätze frei...", - "passenger.assign.seats": "{{count}} verfügbare Plätze", + "passenger.assign.seats_other": "{{count}} verfügbare Plätze", "passenger.assign.seats_zero": "Voll", "passenger.assign.title": "Einen Passagier zuordnen", - "passenger.availability.seats": "{{count}} freier Platz", - "passenger.availability.seats_plural": "{{count}} freie Plätze", + "passenger.availability.seats_one": "{{count}} freier Platz", + "passenger.availability.seats_other": "{{count}} freie Plätze", "passenger.deleted": "Der Passagier wurde aus der Veranstaltung gelöscht.", "passenger.errors.cant_add_passenger": "Passagier kann nicht hinzugefügt werden", "passenger.errors.cant_select_travel": "Kann den Beifahrer nicht bewegen",
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -16,12 +16,12 @@ "dashboard.actions.see_event": "Go to Caroster",

"dashboard.noEvent.create_event": "$t(menu.new_event)", "dashboard.noEvent.text_html": "Here you will see <strong> the Carosters you are participating in </strong>, to start creating a Caroster!", "dashboard.noEvent.title": "Welcome to Caroster", - "dashboard.sections.future": "Caroster to come", - "dashboard.sections.future_plural": "Carosters to come", - "dashboard.sections.noDate": "Caroster without date", - "dashboard.sections.noDate_plural": "Carosters without date", - "dashboard.sections.past": "Caroster passed", - "dashboard.sections.past_plural": "Past Carosters", + "dashboard.sections.future_one": "Caroster to come", + "dashboard.sections.future_other": "Carosters to come", + "dashboard.sections.noDate_one": "Caroster without date", + "dashboard.sections.noDate_other": "Carosters without date", + "dashboard.sections.past_one": "Caroster passed", + "dashboard.sections.past_other": "Past Carosters", "dashboard.title": "$t(menu.dashboard)", "date.today": "Today", "drawer.alerts": "Alerts",

@@ -125,6 +125,7 @@ "notification.type.NewPassengerInYourTrip.content": "A passenger has been added to your trip.",

"notification.type.NewTrip.content": "A new trip close to you is available.", "notification.type.NewTripAlert.content": "A new trip close to you is available.", "notification.type.PassengerJoinTrip.content": "A new passenger wishes to contact you to travel with you.", + "notification.type.AssignedByAdmin.content": "An administrator added you to a trip.", "notifications.content": "No notification", "notifications.markAllRead": "Mark all in read", "notifications.title": "Notifications",

@@ -149,11 +150,12 @@ "passenger.assign.availableCars": "Available trips",

"passenger.assign.departure": "Departure: ", "passenger.assign.no_travel.desc": "{{name}} will receive an email when new trips are available. You can share the event in the meantime.", "passenger.assign.no_travel.title": "No available seats at the moment...", - "passenger.assign.seats": "{{count}} available seats", + "passenger.assign.seats_one": "{{count}} available seat", + "passenger.assign.seats_other": "{{count}} available seats", "passenger.assign.seats_zero": "Full", "passenger.assign.title": "Assign a passenger", - "passenger.availability.seats": "{{count}} seat available", - "passenger.availability.seats_plural": "{{count}} seats available", + "passenger.availability.seats_one": "{{count}} seat available", + "passenger.availability.seats_other": "{{count}} seats available", "passenger.deleted": "The passenger has been deleted from the event.", "passenger.errors.cant_add_passenger": "Unable to add a passenger", "passenger.errors.cant_remove_passenger": "Unable to remove the passenger",

@@ -171,6 +173,8 @@ "passenger.success.added_to_car": "{{name}} has been added to the trip",

"passenger.success.added_to_waitlist": "{{name}} has been added to the waitlist", "passenger.success.goToTravels": "Go to trips", "passenger.title": "Waiting list", + "passenger.waitingList.empty": "Waiting list is empty", + "passenger.waitingList.helper": "This waiting list is only visible to administrators.\nIt allows to see who has activated an alarm with its desired location and its desired proximity radius.\n\nWhen a passenger leaves a trip, they are automatically placed on the waiting list.\nWhen an administrator places a passenger on a trip, the passenger is informed by email that he has been added to a trip and must contact the driver to establish a connection.", "placeInput.mapboxUnavailable": "We are not able to suggest geolocated places at the moment", "placeInput.noCoordinates": "This place is not geo-located and won't appear on the map", "placeInput.item.noCoordinates": "No coordinates",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -16,12 +16,12 @@ "dashboard.actions.see_event": "Accéder au caroster",

"dashboard.noEvent.create_event": "$t(menu.new_event)", "dashboard.noEvent.text_html": "Ici, vous y verrez <strong>les carosters auxquels vous participez</strong>, pour commencer créer un Caroster !", "dashboard.noEvent.title": "Bienvenue sur Caroster", - "dashboard.sections.future": "Caroster à venir", - "dashboard.sections.future_plural": "Carosters à venir", - "dashboard.sections.noDate": "Caroster sans date", - "dashboard.sections.noDate_plural": "Carosters sans date", - "dashboard.sections.past": "Caroster passé", - "dashboard.sections.past_plural": "Carosters passés", + "dashboard.sections.future_one": "Caroster à venir", + "dashboard.sections.future_other": "Carosters à venir", + "dashboard.sections.noDate_one": "Caroster sans date", + "dashboard.sections.noDate_other": "Carosters sans date", + "dashboard.sections.past_one": "Caroster passé", + "dashboard.sections.past_other": "Carosters passés", "dashboard.title": "$t(menu.dashboard)", "date.today": "Aujourd'hui", "drawer.alerts": "Alertes",

@@ -125,6 +125,7 @@ "notification.type.NewPassengerInYourTrip.content": "Un passager a été ajouté à votre trajet.",

"notification.type.NewTrip.content": "Un nouveau trajet proche de chez vous est disponible.", "notification.type.NewTripAlert.content": "Un nouveau trajet proche de chez vous est disponible.", "notification.type.PassengerJoinTrip.content": "Un passager souhaite prendre contact pour voyager avec vous.", + "notification.type.AssignedByAdmin.content": "Un administrateur vous a assigné à un trajet.", "notifications.content": "Aucune notification", "notifications.markAllRead": "Tout marquer en lu", "notifications.title": "Notifications",

@@ -152,8 +153,8 @@ "passenger.assign.no_travel.title": "Pas de place disponible en ce moment...",

"passenger.assign.seats": "{{count}} places disponibles", "passenger.assign.seats_zero": "Complet", "passenger.assign.title": "Placer le passager", - "passenger.availability.seats": "{{count}} place disponible", - "passenger.availability.seats_plural": "{{count}} places disponibles", + "passenger.availability.seats_one": "{{count}} place disponible", + "passenger.availability.seats_other": "{{count}} places disponibles", "passenger.deleted": "Le passager a été supprimé de l'événement.", "passenger.errors.cant_add_passenger": "Impossible d'ajouter un passager", "passenger.errors.cant_remove_passenger": "Impossible de retirer le passager",

@@ -171,6 +172,8 @@ "passenger.success.added_to_car": "{{name}} a été ajouté au trajet",

"passenger.success.added_to_waitlist": "{{name}} ajouté à la liste d'attente", "passenger.success.goToTravels": "Aller aux trajets", "passenger.title": "Liste d'attente", + "passenger.waitingList.empty": "La liste d'attente est vide", + "passenger.waitingList.helper": "Cette liste d'attente est visible seulement par les administrateurs de l'événement.\nElle permet de voir qui a activé une alarme avec sa localisation et son rayon de proximité souhaités.\n\nLorsqu'un passager quitte un trajet, il est automatiquement placé sur la liste d'attente.\nLorsqu'un administrateur place un passager sur un trajet, le passager est informé par email qu'il a été ajouté à un trajet et doit contacter le conducteur pour établir une connexion.", "placeInput.mapboxUnavailable": "Nous ne pouvons pas suggérer de lieux géolocalisés pour le moment", "placeInput.noCoordinates": "Le lieu indiqué n'a pas pu être géo-localisé et ne sera pas affiché sur la carte. Essayez une adresse plus précise.", "placeInput.item.noCoordinates": "Pas de coordonnées",
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -16,12 +16,12 @@ "dashboard.actions.see_event": "Ga naar caroster",

"dashboard.noEvent.create_event": "$t(menu.new_event)", "dashboard.noEvent.text_html": "Hier ziet u <strong>de carosters waaraan u deelneemt</strong> om een Caroster aan te kunnen maken!", "dashboard.noEvent.title": "Welkom bij Caroster", - "dashboard.sections.future": "Aankomende caroster", - "dashboard.sections.future_plural": "Aankomende carosters", - "dashboard.sections.noDate": "Caroster zonder datum", - "dashboard.sections.noDate_plural": "Caroster zonder datums", - "dashboard.sections.past": "Afgelopen caroster", - "dashboard.sections.past_plural": "Afgelopen carosters", + "dashboard.sections.future_one": "Aankomende caroster", + "dashboard.sections.future_other": "Aankomende carosters", + "dashboard.sections.noDate_one": "Caroster zonder datum", + "dashboard.sections.noDate_other": "Caroster zonder datums", + "dashboard.sections.past_one": "Afgelopen caroster", + "dashboard.sections.past_other": "Afgelopen carosters", "dashboard.title": "$t(menu.dashboard)", "date.today": "Vandaag", "drawer.alerts": "Meldingen",

@@ -152,8 +152,8 @@ "passenger.assign.no_travel.title": "Er zijn momenteel geen beschikbare plaatsen…",

"passenger.assign.seats": "{{count}} zitplaatsen beschikbaar", "passenger.assign.seats_zero": "Volledig", "passenger.assign.title": "Toewijzen de passagier", - "passenger.availability.seats": "Er is {{count}} plaats beschikbaar", - "passenger.availability.seats_plural": "Er zijn {{count}} plaatsen beschikbaar", + "passenger.availability.seats_one": "Er is {{count}} plaats beschikbaar", + "passenger.availability.seats_other": "Er zijn {{count}} plaatsen beschikbaar", "passenger.deleted": "De passagier is verwijderd.", "passenger.errors.cant_add_passenger": "De passagier kan niet worden toegevoegd", "passenger.errors.cant_remove_passenger": "De passagier kan niet worden verwijderd",
M frontend/locales/sv.jsonfrontend/locales/sv.json

@@ -8,12 +8,12 @@ "dashboard.actions.see_event": "Gå till caroster",

"dashboard.noEvent.create_event": "$t(menu.new_event)", "dashboard.noEvent.text_html": "Här kommer du att se <strong> de carosters du deltar i </strong>, för att börja skapa en Caroster!", "dashboard.noEvent.title": "Välkommen till Caroster", - "dashboard.sections.future": "Kommande Caroster", - "dashboard.sections.future_plural": "Kommande Carosters", - "dashboard.sections.noDate": "Caroster utan datum", - "dashboard.sections.noDate_plural": "Carosters utan datum", - "dashboard.sections.past": "", - "dashboard.sections.past_plural": "", + "dashboard.sections.future_one": "Kommande Caroster", + "dashboard.sections.future_other": "Kommande Carosters", + "dashboard.sections.noDate_one": "Caroster utan datum", + "dashboard.sections.noDate_other": "Carosters utan datum", + "dashboard.sections.past_one": "", + "dashboard.sections.past_other": "", "dashboard.title": "$t(menu.dashboard)", "drawer.information": "", "drawer.options": "",

@@ -133,8 +133,8 @@ "passenger.assign.no_travel.title": "",

"passenger.assign.seats": "", "passenger.assign.seats_0": "Full", "passenger.assign.title": "", - "passenger.availability.seats": "", - "passenger.availability.seats_plural": "", + "passenger.availability.seats_one": "", + "passenger.availability.seats_other": "", "passenger.deleted": "", "passenger.errors.cant_add_passenger": "", "passenger.errors.cant_remove_passenger": "",
A frontend/pages/e/[uuid]/adminWaitingList.tsx

@@ -0,0 +1,64 @@

+import {PropsWithChildren} from 'react'; +import EventLayout, {TabComponent} from '../../../layouts/Event'; +import pageUtils from '../../../lib/pageUtils'; +import {EventByUuidDocument} from '../../../generated/graphql'; +import TripAlertsList from '../../../containers/TripAlertsList'; + +interface Props { + eventUUID: string; + announcement?: string; +} + +const Page = (props: PropsWithChildren<Props>) => { + return <EventLayout {...props} Tab={WaitingListTab} tabProps={props} />; +}; + +const WaitingListTab: TabComponent<Props> = ({event}) => { + return <TripAlertsList />; +}; + +export const getServerSideProps = pageUtils.getServerSideProps( + async (context, apolloClient, session) => { + const {uuid} = context.query; + const {host = ''} = context.req.headers; + let event = null; + + // Fetch event + try { + const {data} = await apolloClient.query({ + query: EventByUuidDocument, + variables: {uuid}, + }); + event = data?.eventByUUID?.data; + } catch (error) { + return { + notFound: true, + }; + } + + const isCarosterPlus = + event?.attributes?.enabled_modules?.includes('caroster-plus'); + if (!isCarosterPlus) + return { + notFound: true, + }; + + const userEmail = session?.user?.email; + const userIsAdmin = + event?.attributes?.adminstrators?.includes(userEmail) || + event?.attributes?.email === userEmail; + if (!userIsAdmin) return {notFound: true}; + + return { + props: { + eventUUID: uuid, + metas: { + title: event?.attributes?.name || '', + url: `https://${host}${context.resolvedUrl}`, + }, + }, + }; + } +); + +export default Page;
M frontend/pages/e/[uuid]/alerts.tsxfrontend/pages/e/[uuid]/alerts.tsx

@@ -55,6 +55,13 @@ notFound: true,

}; } + const isCarosterPlus = + event?.attributes?.enabled_modules?.includes('caroster-plus'); + if (!isCarosterPlus) + return { + notFound: true, + }; + try { const {data} = await apolloClient.query({ query: TripAlertDocument,
M frontend/pages/e/[uuid]/options.tsxfrontend/pages/e/[uuid]/options.tsx

@@ -13,7 +13,7 @@ Enum_Userspermissionsuser_Lang as SupportedLocales,

} from '../../../generated/graphql'; import CarosterPlusOption from '../../../containers/CarosterPlusOption'; import CarosterPlusSettings from '../../../containers/CarosterPlusSettings'; -import {Card, Typography} from '@mui/material'; +import {Typography} from '@mui/material'; import {useTranslation} from 'next-i18next'; interface Props {