feat: ✨ Improve 'Add myself' feature #337
Simon Mulquin simon@octree.ch
Mon, 19 Sep 2022 09:54:41 +0000
7 files changed,
129 insertions(+),
28 deletions(-)
M
backend/.strapi-updater.json
→
backend/.strapi-updater.json
@@ -1,5 +1,5 @@
{ "latest": "4.3.8", - "lastUpdateCheck": 1663340063052, + "lastUpdateCheck": 1663575220337, "lastNotification": 1663340063046 }
A
backend/src/graphql/passenger/createPassenger.ts
@@ -0,0 +1,102 @@
+const createPassenger = { + description: "Create a passenger", + async resolve(_root, args) { + const { data: passengerInput } = args; + + const { user: userId, travel: travelId } = passengerInput; + + try { + //Avoid duplicity when the connected users add themself to a new car + if (userId && travelId) { + const travel = await strapi.entityService.findOne( + "api::travel.travel", + travelId, + { populate: ["event"] } + ); + + const userPassengersIds = ( + await strapi.entityService.findMany("api::passenger.passenger", { + filters: { + user: userId, + }, + }) + ).map((userPassenger) => userPassenger.id); + + const travelsIdsBelongingToUserInEvent = ( + await strapi.entityService.findMany("api::travel.travel", { + filters: { + event: travel.event.id, + passengers: { id: { $containsi: userPassengersIds } }, + }, + }) + ).map((travel) => travel.id); + + const userDuplicatesinEvent = await strapi.entityService.findMany( + "api::passenger.passenger", + { + filters: { + $or: [ + { + event: travel.event.id, + user: userId, + }, + { + travel: { id: { $in: travelsIdsBelongingToUserInEvent } }, + user: userId, + }, + ], + }, + } + ); + + if (userDuplicatesinEvent.length > 0) { + const [existingPassenger, ...duplicated] = userDuplicatesinEvent; + + await Promise.all( + duplicated?.map(async (passenger) => { + await strapi.entityService.delete( + "api::passenger.passenger", + passenger.id + ); + }) + ); + + return makeResponse({ + operation: strapi.entityService.update( + "api::passenger.passenger", + existingPassenger.id, + { data: { ...passengerInput, event: null } } + ), + args, + }); + } + } + + return makeResponse({ + operation: await strapi.entityService.create( + "api::passenger.passenger", + { + data: passengerInput, + } + ), + args, + }); + } catch (error) { + console.log(error); + throw new Error("Couldn't create the passenger"); + } + }, +}; + +const makeResponse = async ({ operation, args }) => { + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + + return toEntityResponse(await operation, { + args, + resourceUID: "api::passenger.passenger", + }); +}; + +export default createPassenger;
M
backend/src/graphql/passenger/index.ts
→
backend/src/graphql/passenger/index.ts
@@ -1,3 +1,5 @@
+import createPassenger from "./createPassenger"; + export default [ ({ nexus, strapi }) => ({ resolvers: {@@ -47,6 +49,9 @@ resourceUID: "api::vehicle.vehicle",
}); }, }, + }, + Mutation: { + createPassenger }, }, }),
M
frontend/containers/PassengersList/Passenger.tsx
→
frontend/containers/PassengersList/Passenger.tsx
@@ -12,17 +12,17 @@
interface Props { passenger?: PassengerEntity; button?: ReactNode; - isVehicle?: boolean; + isTravel?: boolean; } const Passenger = (props: Props) => { - const {passenger, button, isVehicle} = props; + const {passenger, button, isTravel} = props; const {t} = useTranslation(); const classes = useStyles(); const {userId} = useProfile(); const isUser = `${userId}` === passenger?.attributes.user?.data?.id; - const showLocation = isVehicle ? false : passenger.attributes.location; + const showLocation = isTravel ? false : passenger.attributes.location; if (passenger) { return (
M
frontend/containers/PassengersList/index.tsx
→
frontend/containers/PassengersList/index.tsx
@@ -14,7 +14,6 @@ onClick: () => void;
disabled?: boolean; }) => JSX.Element; disabled?: boolean; - isVehicle?: boolean; isTravel?: boolean; places?: number; onPress?: (passengerId: string) => void;@@ -22,7 +21,7 @@ onClick?: (passengerId: string) => void;
} const PassengersList = (props: Props) => { - const {passengers, places, Button, onClick, onPress, disabled, isVehicle} = + const {passengers, places, Button, onClick, onPress, disabled, isTravel} = props; const classes = useStyles(); let list = passengers;@@ -49,7 +48,7 @@ >
<Passenger key={index} passenger={passenger} - isVehicle={isVehicle} + isTravel={isTravel} button={ <Button onClick={() => onClick && onClick(passenger.id)}
M
frontend/containers/Travel/index.tsx
→
frontend/containers/Travel/index.tsx
@@ -1,4 +1,4 @@
-import {useReducer} from 'react'; +import {useMemo, useReducer} from 'react'; import {makeStyles} from '@material-ui/core/styles'; import Divider from '@material-ui/core/Divider'; import Paper from '@material-ui/core/Paper';@@ -9,11 +9,11 @@ import AddPassengerButtons from '../AddPassengerButtons';
import HeaderEditing from './HeaderEditing'; import Header from './Header'; import useActions from './useActions'; +import useProfile from '../../hooks/useProfile'; interface Props { travel: TravelType & {id: string}; getAddPassengerFunction: (addSelf: boolean) => () => void; - canAddSelf: boolean; } const Travel = (props: Props) => {@@ -21,10 +21,20 @@ const {travel} = props;
const classes = useStyles(); const [isEditing, toggleEditing] = useReducer(i => !i, false); const actions = useActions({travel}); + const {userId, connected} = useProfile(); if (!travel) return null; const disableNewPassengers = travel.passengers.data?.length >= travel.seats; + const canAddSelf = useMemo(() => { + if (!connected) return false; + const isInTravel = travel.passengers?.data.some( + passenger => passenger.attributes.user?.data?.id === `${userId}` + ); + + return !isInTravel; + }, [travel, userId]); + return ( <Paper className={classes.root}> {isEditing ? (@@ -35,7 +45,7 @@ )}
<Divider /> <AddPassengerButtons getOnClickFunction={props.getAddPassengerFunction} - canAddSelf={props.canAddSelf} + canAddSelf={canAddSelf} variant="travel" disabled={disableNewPassengers} />@@ -45,11 +55,10 @@ <PassengersList
passengers={travel.passengers.data} places={travel?.seats} onClick={actions.sendPassengerToWaitingList} - isVehicle + isTravel Button={({onClick}: {onClick: () => void}) => ( <ClearButton icon="close" onClick={onClick} tabIndex={-1} /> )} - isTravel /> )} </Paper>
M
frontend/containers/TravelColumns/index.tsx
→
frontend/containers/TravelColumns/index.tsx
@@ -1,4 +1,4 @@
-import {useMemo, useRef, useState} from 'react'; +import {useRef, useState} from 'react'; import {makeStyles} from '@material-ui/core/styles'; import Container from '@material-ui/core/Container'; import Slider from 'react-slick';@@ -27,7 +27,7 @@ const slider = useRef(null);
const {t} = useTranslation(); const addToast = useToastStore(s => s.addToast); const {addToEvent} = useAddToEvents(); - const {profile, userId, connected} = useProfile(); + const {profile, userId} = useProfile(); const classes = useStyles(); const [newPassengerTravelContext, toggleNewPassengerToTravel] = useState<{ travel: TravelType;@@ -35,19 +35,6 @@ } | null>(null);
const {addPassenger} = usePassengersActions(); const sortedTravels = travels?.slice().sort(sortTravels); - const canAddSelf = useMemo(() => { - if (!connected) return false; - const isInWaitingList = event?.waitingPassengers?.data.some( - passenger => passenger.attributes.user?.data?.id === `${userId}` - ); - const isInTravel = event?.travels?.data.some(travel => - travel.attributes.passengers?.data.some( - passenger => passenger.attributes.user?.data?.id === `${userId}` - ) - ); - return !(isInWaitingList || isInTravel); - }, [event, userId]); - const addSelfToTravel = async (travel: TravelType) => { try { await addPassenger({@@ -85,7 +72,6 @@ >
<Travel travel={travel} {...props} - canAddSelf={canAddSelf} getAddPassengerFunction={(addSelf: boolean) => () => addSelf ? addSelfToTravel(travel)