all repos — caroster @ 589ba138174504e3663d8e893cf9bdaed9e25d59

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

feat: :lock: Set permissions for Caroster plus
Tim Izzo tim@octree.ch
Mon, 12 Feb 2024 10:34:46 +0000
commit

589ba138174504e3663d8e893cf9bdaed9e25d59

parent

23027a122b6cf9f8018dc50a51bb031d35f1598b

M backend/config/permissions.tsbackend/config/permissions.ts

@@ -1,16 +1,6 @@

const publicPerms = [ - "api::travel.travel.create", - "api::travel.travel.delete", - "api::travel.travel.update", - "api::vehicle.vehicle.create", - "api::vehicle.vehicle.delete", - "api::vehicle.vehicle.update", "api::event.event.create", "api::event.event.findOne", - "api::event.event.update", - "api::passenger.passenger.create", - "api::passenger.passenger.delete", - "api::passenger.passenger.update", "api::page.page.find", "api::page.page.findOne", "api::module.module.find",

@@ -18,10 +8,7 @@ "api::setting.setting.find",

"api::stripe.stripe.handleWebhook", ]; -const authenticated = [ - ...publicPerms, - "plugin::users-permissions.user.me", -]; +const authenticated = [...publicPerms, "plugin::users-permissions.user.me"]; export default { roles: {
A backend/src/api/event/policies/check-caroster-plus.ts

@@ -0,0 +1,19 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, _config, { strapi }) => { + const { uuid } = policyContext.args; + + const event = await strapi.db + .query("api::event.event") + .findOne({ where: { uuid } }); + + if (!event) throw new errors.NotFoundError(`Event not found`); + + if (event.enabled_modules?.includes("caroster-plus")) { + const user = policyContext.state.user; + if (!user) throw new errors.ForbiddenError(); + + const admins = event.administrators?.split(/, ?/) || []; + return [...admins, event.email].includes(user.email); + } +};
A backend/src/api/passenger/policies/add-only-self.ts

@@ -0,0 +1,13 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext) => { + 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) + throw new errors.UnauthorizedError("Can't add linked user as anonymous"); + } +};
A backend/src/api/passenger/policies/check-creation.ts

@@ -0,0 +1,14 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, _config, { strapi }) => { + const user = policyContext.state.user; + const eventId = policyContext.args?.data?.event; + 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(); + } +};
A backend/src/api/passenger/policies/check-deletion.ts

@@ -0,0 +1,30 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, _config, { strapi }) => { + const passengerId = policyContext.args?.id; + const passenger = await strapi.entityService.findOne( + "api::passenger.passenger", + passengerId, + { + populate: ["event", "user"], + } + ); + + if (!passenger) throw new errors.NotFoundError("Passenger not found"); + + const event = passenger.event; + + if (event.enabled_modules?.includes("caroster-plus")) { + const user = policyContext.state.user; + if (!user) throw new errors.ForbiddenError(); + else if (!passenger.user) return true; + + const admins = event.administrators?.split(/, ?/) || []; + const isAdmin = [...admins, event.email].includes(user.email); + if (isAdmin) { + // TODO Create notification to passenger's linked user + return true; + } else if (passenger.user.id == user.id) return true; + else return false; + } +};
A backend/src/api/passenger/policies/check-update.ts

@@ -0,0 +1,28 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, _config, { strapi }) => { + const passengerId = policyContext.args?.id; + const passenger = await strapi.entityService.findOne( + "api::passenger.passenger", + passengerId, + { + populate: ["event", "user"], + } + ); + + if (!passenger) throw new errors.NotFoundError("Passenger not found"); + + const event = passenger.event; + + if (event.enabled_modules?.includes("caroster-plus")) { + const user = policyContext.state.user; + if (!user) throw new errors.ForbiddenError(); + else if (!passenger.user) return true; + + const admins = event.administrators?.split(/, ?/) || []; + const isAdmin = [...admins, event.email].includes(user.email); + if (isAdmin) return true; + else if (passenger.user.id == user.id) return true; + else return false; + } +};
A backend/src/api/travel/policies/check-creation.ts

@@ -0,0 +1,12 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, config, { strapi }) => { + const eventId = policyContext.args?.data?.event; + const event = await strapi.entityService.findOne("api::event.event", eventId); + + if (!event) throw new errors.NotFoundError(`Event not found`); + + const user = policyContext.state.user; + if (event.enabled_modules?.includes("caroster-plus") && !user) + throw new errors.ForbiddenError(); +};
A backend/src/api/travel/policies/check-deletion.ts

@@ -0,0 +1,30 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, config, { strapi }) => { + const travelId = policyContext.args?.id; + const travel = await strapi.entityService.findOne( + "api::travel.travel", + travelId, + { + populate: ["event", "user"], + } + ); + + if (!travel) throw new errors.NotFoundError(`Travel not found`); + + const event = travel.event; + + if (event.enabled_modules?.includes("caroster-plus")) { + const user = policyContext.state.user; + if (!user) throw new errors.ForbiddenError(); + + const admins = event.administrators?.split(/, ?/) || []; + const isAdmin = [...admins, event.email].includes(user.email); + + if (isAdmin) { + // TODO Create notification to travel's linked user + return true; + } else if (travel.user?.email === user.email) return true; + else return false; + } +};
A backend/src/api/travel/policies/check-update.ts

@@ -0,0 +1,32 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, _config, { strapi }) => { + const travelId = policyContext.args?.id; + const travel = await strapi.entityService.findOne( + "api::travel.travel", + travelId, + { + populate: ["event", "user"], + } + ); + + if (!travel) throw new errors.NotFoundError(`Travel not found`); + + const event = travel.event; + + const eventId = policyContext.args?.data?.event; + if (eventId !== event.id) + throw new errors.UnauthorizedError("Can't change travel linked event"); + + if (event.enabled_modules?.includes("caroster-plus")) { + const user = policyContext.state.user; + if (!user) throw new errors.ForbiddenError(); + + const admins = event.administrators?.split(/, ?/) || []; + const isAdmin = [...admins, event.email].includes(user.email); + + if (isAdmin) return true; + else if (travel.user?.email === user.email) return true; + else return false; + } +};
A backend/src/api/vehicle/policies/check-creation.ts

@@ -0,0 +1,14 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, config, { strapi }) => { + const user = policyContext.state.user; + if (!user) + throw new errors.ForbiddenError( + "Only authenticated user can create vehicle." + ); + + if (policyContext.args?.data?.user !== user.id) + throw new errors.UnauthorizedError( + "Can only create vehicle for authenticated user." + ); +};
A backend/src/api/vehicle/policies/check-deletion.ts

@@ -0,0 +1,21 @@

+import { errors } from "@strapi/utils"; + +export default async (policyContext, config, { strapi }) => { + const vehicleId = policyContext.args?.id; + const vehicle = await strapi.entityService.findOne( + "api::vehicle.vehicle", + vehicleId, + { + populate: ["user"], + } + ); + + if (!vehicle) throw new errors.NotFoundError(`Vehicle not found`); + + const user = policyContext.state.user; + + if (vehicle.user?.id !== user.id) + throw new errors.UnauthorizedError( + "Can only delete vehicle linked to authenticated user." + ); +};
M backend/src/graphql/event/event.tsbackend/src/graphql/event/event.ts

@@ -112,14 +112,11 @@ },

}, resolversConfig: { "Query.eventByUUID": { - auth: { - scope: ["api::event.event.findOne"], - }, + auth: false, }, "Mutation.updateEventByUUID": { - auth: { - scope: ["api::event.event.update"], - }, + auth: false, + policies: ["api::event.check-caroster-plus"], }, "Event.passengers": { auth: false,
M backend/src/graphql/index.tsbackend/src/graphql/index.ts

@@ -19,6 +19,5 @@ notificationExtensions.forEach(extService.use);

tripAlert.forEach(extService.use); // Disable shadow CRUD - /// Fields extService.shadowCRUD("api::event.event").field("users").disableOutput(); };
M backend/src/graphql/passenger/index.tsbackend/src/graphql/passenger/index.ts

@@ -1,62 +1,35 @@

import createPassenger from "./createPassenger"; +import updatePassenger from "./updatePassenger"; export default [ - ({ nexus, strapi }) => ({ + ({ strapi }) => ({ resolvers: { - Passenger: { - // Filter user private fields in passenger lists - user: { - async resolve(parent) { - const passenger = await strapi.entityService.findOne( - "api::passenger.passenger", - parent.id, - { populate: ["user"] } - ); - const user = passenger?.user; - if (!user) return null; - - return { - value: { - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - lang: user.lang, - }, - }; - }, - }, - }, - UsersPermissionsUser: { - // Filter user vehicles if not for profile fetching - vehicles: { - async resolve(queriedUser, args, _context, query) { - if (query.path.prev.key !== "profile") return null; - - const user = await strapi.entityService.findOne( - "plugin::users-permissions.user", - queriedUser.id, - { populate: ["vehicles"] } - ); - if (!user?.vehicles) return null; - - const { toEntityResponseCollection } = strapi - .plugin("graphql") - .service("format").returnTypes; - - return toEntityResponseCollection(user.vehicles, { - args, - resourceUID: "api::vehicle.vehicle", - }); - }, - }, - }, Mutation: { createPassenger, + updatePassenger, }, }, resolversConfig: { "Passenger.user": { auth: false, + }, + "Passenger.travel": { + auth: false, + }, + "Mutation.createPassenger": { + auth: false, + policies: [ + "api::passenger.add-only-self", + "api::passenger.check-creation", + ], + }, + "Mutation.updatePassenger": { + auth: false, + policies: ["api::passenger.check-update"], + }, + "Mutation.deletePassenger": { + auth: false, + policies: ["api::passenger.check-deletion"], }, }, }),
A backend/src/graphql/passenger/updatePassenger.ts

@@ -0,0 +1,24 @@

+const updatePassenger = { + description: "Update a passenger", + async resolve(_root, args) { + const { id, data } = args; + try { + const passenger = await strapi.entityService.update( + "api::passenger.passenger", + id, + { data } + ); + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponse(passenger, { + args, + resourceUID: "api::passenger.passenger", + }); + } catch (error) { + strapi.log.error(error); + } + }, +}; + +export default updatePassenger;
M backend/src/graphql/travel/index.tsbackend/src/graphql/travel/index.ts

@@ -54,6 +54,18 @@ },

}, }, resolversConfig: { + "Mutation.createTravel": { + auth: false, + policies: ["api::travel.check-creation"], + }, + "Mutation.updateTravel": { + auth: false, + policies: ["api::travel.check-update"], + }, + "Mutation.deleteTravel": { + auth: false, + policies: ["api::travel.check-deletion"], + }, "Travel.passengers": { auth: false, },
M backend/src/graphql/user/index.tsbackend/src/graphql/user/index.ts

@@ -90,6 +90,30 @@ });

}, }, }, + // Filter user fields if not for profile fetching + UsersPermissionsUser: { + vehicles: { + async resolve(queriedUser, args, _context, query) { + if (query.path.prev.key !== "profile") return null; + + const user = await strapi.entityService.findOne( + "plugin::users-permissions.user", + queriedUser.id, + { populate: ["vehicles"] } + ); + if (!user?.vehicles) return null; + + const { toEntityResponseCollection } = strapi + .plugin("graphql") + .service("format").returnTypes; + + return toEntityResponseCollection(user.vehicles, { + args, + resourceUID: "api::vehicle.vehicle", + }); + }, + }, + }, }, resolversConfig: { "Query.me": {

@@ -97,17 +121,38 @@ auth: {

scope: ["plugin::users-permissions.user.me"], }, }, - "UsersPermissionsUser.events": { + "UsersPermissionsUser.vehicles": { auth: true, }, - "UsersPermissionsUser.vehicles": { - auth: true, + "UsersPermissionsUser.events": { + policies: [checkAuthUser], }, "UsersPermissionsUser.notifications": { - auth: { - scope: ["plugin::users-permissions.user.me"], - }, + policies: [checkAuthUser], + }, + "UsersPermissionsUser.confirmed": { + policies: [checkAuthUser], + }, + "UsersPermissionsUser.provider": { + policies: [checkAuthUser], + }, + "UsersPermissionsUser.newsletterConsent": { + policies: [checkAuthUser], + }, + "UsersPermissionsUser.createdAt": { + policies: [checkAuthUser], + }, + "UsersPermissionsUser.onboardingCreator": { + policies: [checkAuthUser], + }, + "UsersPermissionsUser.onboardingUser": { + policies: [checkAuthUser], }, }, }), ]; + +const checkAuthUser = (context) => { + const authUser = context.state.user; + return context.parent.id === authUser.id; +};
M backend/src/graphql/vehicle/index.tsbackend/src/graphql/vehicle/index.ts

@@ -28,5 +28,15 @@ },

}, }, }, + resolversConfig: { + "Mutation.createVehicle": { + auth: true, + policies: ["api::vehicle.check-creation"], + }, + "Mutation.deleteVehicle": { + auth: true, + policies: ["api::vehicle.check-deletion"], + }, + }, }), ];
M frontend/containers/AddPassengerButtons/index.tsxfrontend/containers/AddPassengerButtons/index.tsx

@@ -5,7 +5,8 @@ import {useTranslation} from 'react-i18next';

import usePermissions from '../../hooks/usePermissions'; interface Props { - getOnClickFunction: (addSelf: boolean) => () => void; + onAddSelf: () => void; + onAddOther: () => void; registered: boolean; variant: 'waitingList' | 'travel'; disabled?: boolean;

@@ -17,7 +18,8 @@ travel: 'travel.passengers.add_to_travel',

}; const AddPassengerButtons = ({ - getOnClickFunction, + onAddSelf, + onAddOther, registered, variant, disabled,

@@ -45,7 +47,7 @@ sx={textSx}

variant="contained" color="primary" fullWidth - onClick={getOnClickFunction(true)} + onClick={onAddSelf} disabled={disabled || registered} > {t(

@@ -63,7 +65,7 @@ sx={textSx}

variant="outlined" color="primary" fullWidth - onClick={getOnClickFunction(false)} + onClick={onAddOther} disabled={disabled} > {t(ADD_TO_LOCALE[variant])}
M frontend/containers/AssignPassenger/AvailableTravel.tsxfrontend/containers/AssignPassenger/AvailableTravel.tsx

@@ -8,21 +8,17 @@ import Divider from '@mui/material/Divider';

import LinearProgress from '@mui/material/LinearProgress'; import {useTranslation} from 'react-i18next'; import getMapsLink from '../../lib/getMapsLink'; -import {Travel} from '../../generated/graphql'; - -type TravelObject = Travel & {id: string}; - -export type SelectTravel = (travel: TravelObject) => Promise<void>; +import {Travel, TravelEntity} from '../../generated/graphql'; interface Props { - travel: TravelObject; - select: SelectTravel; + travel: TravelEntity; + assign: (travel: TravelEntity) => void; } -const AvailableTravel = ({travel, select}: Props) => { +const AvailableTravel = ({travel, assign}: Props) => { const {t} = useTranslation(); - const passengersCount = travel.passengers?.data.length || 0; - const availableSeats = travel.seats - passengersCount || 0; + const passengersCount = travel.attributes.passengers?.data.length || 0; + const availableSeats = travel.attributes.seats - passengersCount; return ( <>

@@ -30,14 +26,14 @@ <Divider />

<ListItem sx={{flexDirection: 'column', p: 2}}> <Box display="flex" justifyContent="space-between" width={1}> <Box> - {travel.departure && ( + {travel.attributes.departure && ( <Typography variant="overline" color="GrayText"> {t('passenger.assign.departure')} - {moment(travel.departure).format('LLLL')} + {moment(travel.attributes.departure).format('LLLL')} </Typography> )} <Typography variant="body1" sx={{pt: 1}}> - {travel.vehicleName} + {travel.attributes.vehicleName} </Typography> </Box> <Button

@@ -45,7 +41,7 @@ color="primary"

variant="contained" size="small" sx={{maxHeight: '24px'}} - onClick={() => select(travel)} + onClick={() => assign(travel)} > {t('passenger.assign.assign')} </Button>

@@ -60,8 +56,8 @@ '& .MuiLinearProgress-bar': {

backgroundColor: 'Gray', }, }} - value={(passengersCount / travel.seats || 0) * 100} - variant={travel.seats ? 'determinate' : 'indeterminate'} + value={(passengersCount / travel.attributes.seats || 0) * 100} + variant={travel.attributes.seats ? 'determinate' : 'indeterminate'} /> <Box display="flex" justifyContent="space-between" width={1}> <Typography variant="body1" color="GrayText" minWidth="150px">

@@ -75,15 +71,15 @@ maxWidth: 'calc(100% - 150px)',

overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', - paddingLeft: 2 + paddingLeft: 2, }} variant="overline" target="_blank" rel="noreferrer" - href={getMapsLink(travel.meeting)} + href={getMapsLink(travel.attributes.meeting)} onClick={e => e.preventDefault} > - {travel.meeting} + {travel.attributes.meeting} </Link> </Box> </ListItem>
M frontend/containers/AssignPassenger/index.tsxfrontend/containers/AssignPassenger/index.tsx

@@ -10,7 +10,8 @@ import ShareEvent from '../ShareEvent';

import useToastStore from '../../stores/useToastStore'; import useEventStore from '../../stores/useEventStore'; import usePassengersActions from '../../hooks/usePassengersActions'; -import AvailableTravel, {SelectTravel} from './AvailableTravel'; +import AvailableTravel from './AvailableTravel'; +import {TravelEntity} from '../../generated/graphql'; const AssignPassenger = () => { const {t} = useTranslation();

@@ -26,9 +27,8 @@ query: {passengerId},

} = router; const {updatePassenger} = usePassengersActions(); - if (!event) { - return null; - } + if (!event) return null; + const {travels, name, waitingPassengers, uuid} = event; const availableTravels = travels?.data?.filter(

@@ -40,9 +40,9 @@ const passenger = waitingPassengers.data?.find(

waitingPassenger => waitingPassenger.id === passengerId ); - const assign: SelectTravel = async travel => { + const assign = async (travel: TravelEntity) => { try { - await updatePassenger(passengerId, { + await updatePassenger(passengerId as string, { travel: travel.id, }); addToast(

@@ -76,7 +76,7 @@ <Box sx={{p: 2, textAlign: 'center'}}>

<Typography variant="h4"> {t('passenger.assign.no_travel.title')} </Typography> - <Typography variant="body1" sx={{py: 2}}> + <Typography sx={{py: 2}}> {t('passenger.assign.no_travel.desc', { name: passenger?.attributes?.name, })}

@@ -91,12 +91,12 @@ sx={{p: 2}}

variant="h4" >{t`passenger.assign.availableCars`}</Typography> <List disablePadding> - {availableTravels.map(({id, attributes}, index) => { + {availableTravels.map((travel, index) => { return ( <AvailableTravel key={index} - travel={{id, ...attributes}} - select={assign} + travel={travel} + assign={assign} /> ); })}
M frontend/containers/WaitingList/index.tsxfrontend/containers/WaitingList/index.tsx

@@ -18,20 +18,18 @@ import PassengersList from '../PassengersList';

import RemoveDialog from '../RemoveDialog'; import AddPassengerButtons from '../AddPassengerButtons'; import AssignButton from './AssignButton'; -import { useTheme } from '@mui/material/styles'; +import {useTheme} from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; interface Props { - getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void; canAddSelf: boolean; registered: boolean; + onAddSelf: () => void; + onAddOther: () => void; } -const WaitingList = ({ - getToggleNewPassengerDialogFunction, - canAddSelf, - registered -}: Props) => { +const WaitingList = (props: Props) => { + const {canAddSelf, registered} = props; const {t} = useTranslation(); const event = useEventStore(s => s.event); const theme = useTheme();

@@ -114,7 +112,8 @@ </Typography>

</Box> <Divider /> <AddPassengerButtons - getOnClickFunction={getToggleNewPassengerDialogFunction} + onAddOther={props.onAddOther} + onAddSelf={props.onAddSelf} canAddSelf={canAddSelf} registered={registered} variant="waitingList"
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -131,6 +131,7 @@ "passenger.assign.availableCars": "Available cars",

"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", + "passenger.errors.cant_select_travel": "Can't move the passenger", "passenger.success.added_self_to_car": "You have been added to this car", "passenger.success.added_self_to_waitlist": "You have been added to the waitlist. You'll be notified when new cars will be added.", "passenger.success.added_to_car": "{{name}} has been added to this car",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -131,6 +131,7 @@ "passenger.assign.availableCars": "Voitures 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", + "passenger.errors.cant_select_travel": "Impossible de placer le passager", "passenger.success.added_self_to_car": "Vous avez été ajouté à la voiture", "passenger.success.added_self_to_waitlist": "Vous avez été ajouté à la liste d’attente. Vous serez notifié à l’ajout de nouvelles voitures", "passenger.success.added_to_car": "{{name}} a été ajouté à la voiture",
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -67,7 +67,7 @@ "options.plus.admins": "Beheerders",

"options.plus.creator": "Schepper", "options.plus.activationOK": "Voeg Caroster plus toe aan uw evenement", "options.plus.activationForbiden": "Alleen de beheerder is bevoegd om modules toe te voegen", - "options.plus.addAdmin": "", + "options.plus.addAdmin": "", "options.plus.addAdmin.email": "", "options.plus.addAdmin.emailHelper": "", "options.plus.adminAdded": "",

@@ -124,6 +124,7 @@ "passenger.assign.availableCars": "Beschikbare auto's",

"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", + "passenger.errors.cant_select_travel": "", "passenger.success.added_self_to_car": "U bent toegevoegd aan deze auto", "passenger.success.added_self_to_waitlist": "U bent toegevoegd aan de wachtlijst. U ontvangt een melding zodra er nieuwe auto's beschikbaar zijn.", "passenger.success.added_to_car": "{{name}} is toegevoegd aan deze auto",
M frontend/locales/pl.jsonfrontend/locales/pl.json

@@ -70,7 +70,7 @@ "options.plus.admins": "",

"options.plus.creator": "", "options.plus.activationOK": "", "options.plus.activationForbiden": "", - "options.plus.addAdmin": "", + "options.plus.addAdmin": "", "options.plus.addAdmin.email": "", "options.plus.addAdmin.emailHelper": "", "options.plus.adminAdded": "",

@@ -128,6 +128,7 @@ "passenger.assign.availableCars": "",

"passenger.deleted": "Pasażer został usunięty z wydarzenia.", "passenger.errors.cant_add_passenger": "Nie można dodać pasażera", "passenger.errors.cant_remove_passenger": "Nie można usunąć pasażera", + "passenger.errors.cant_select_travel": "", "passenger.success.added_self_to_car": "Dodano cię do tego samochodu", "passenger.success.added_self_to_waitlist": "Dodano cię do listy oczekujących. Otrzymasz powiadomienie, gdy zostaną dodane nowe samochody.", "passenger.success.added_to_car": "Dodano {{name}} do tego samochodu",
M frontend/locales/sv.jsonfrontend/locales/sv.json

@@ -67,7 +67,7 @@ "options.plus.admins": "",

"options.plus.creator": "", "options.plus.activationOK": "", "options.plus.activationForbiden": "", - "options.plus.addAdmin": "", + "options.plus.addAdmin": "", "options.plus.addAdmin.email": "", "options.plus.addAdmin.emailHelper": "", "options.plus.adminAdded": "",

@@ -124,6 +124,7 @@ "passenger.assign.availableCars": "",

"passenger.deleted": "", "passenger.errors.cant_add_passenger": "", "passenger.errors.cant_remove_passenger": "", + "passenger.errors.cant_select_travel": "", "passenger.success.added_self_to_car": "", "passenger.success.added_self_to_waitlist": "", "passenger.success.added_to_car": "",
M frontend/pages/e/[uuid]/waitingList.tsxfrontend/pages/e/[uuid]/waitingList.tsx

@@ -1,4 +1,4 @@

-import {useState, useMemo, PropsWithChildren} from 'react'; +import {useState, useMemo, PropsWithChildren, useReducer} from 'react'; import useProfile from '../../../hooks/useProfile'; import WaitingList from '../../../containers/WaitingList'; import pageUtils from '../../../lib/pageUtils';

@@ -6,10 +6,6 @@ import EventLayout, {TabComponent} from '../../../layouts/Event';

import {AddPassengerToWaitingList} from '../../../containers/NewPassengerDialog'; import {EventByUuidDocument} from '../../../generated/graphql'; -interface NewPassengerDialogContext { - addSelf: boolean; -} - interface Props { eventUUID: string; announcement?: string;

@@ -21,8 +17,8 @@ };

const WaitingListTab: TabComponent = ({event}) => { const {userId} = useProfile(); - const [addPassengerToWaitingListContext, toggleNewPassengerToWaitingList] = - useState<NewPassengerDialogContext | null>(null); + const [dialogOpen, toggleDialog] = useReducer(i => !i, false); + const [isSelfAdd, setIsSelfAdd] = useState(false); const registered = useMemo(() => { if (!userId) return false;

@@ -37,16 +33,20 @@ <>

<WaitingList registered={registered} canAddSelf={!!userId} - getToggleNewPassengerDialogFunction={(addSelf: boolean) => () => - toggleNewPassengerToWaitingList({addSelf})} + onAddSelf={() => { + setIsSelfAdd(true); + toggleDialog(); + }} + onAddOther={() => { + setIsSelfAdd(false); + toggleDialog(); + }} /> - {!!addPassengerToWaitingListContext && ( - <AddPassengerToWaitingList - open={!!addPassengerToWaitingListContext} - toggle={() => toggleNewPassengerToWaitingList(null)} - addSelf={addPassengerToWaitingListContext.addSelf} - /> - )} + <AddPassengerToWaitingList + open={dialogOpen} + toggle={toggleDialog} + addSelf={isSelfAdd} + /> </> ); };