frontend/containers/Travel/Header.tsx (view raw)
1import moment from 'moment';
2import Linkify from 'linkify-react';
3import Typography from '@mui/material/Typography';
4import IconButton from '@mui/material/IconButton';
5import TuneIcon from '@mui/icons-material/Tune';
6import Box from '@mui/material/Box';
7import Link from '@mui/material/Link';
8import LinearProgress from '@mui/material/LinearProgress';
9import Chip from '@mui/material/Chip';
10import {useTranslation} from 'next-i18next';
11import getMapsLink from '../../lib/getMapsLink';
12import useMapStore from '../../stores/useMapStore';
13import usePermissions from '../../hooks/usePermissions';
14import useProfile from '../../hooks/useProfile';
15import DetailsLink from '../DetailsLink';
16import {TravelEntity} from '../../generated/graphql';
17import {getFormatedPhoneNumber} from '../../lib/phoneNumbers';
18import useEventStore from '../../stores/useEventStore';
19import {getTravelName} from '../../lib/travels';
20import theme from '../../theme';
21
22interface Props {
23 travel: TravelEntity;
24 toggleEditing: () => void;
25}
26
27const MAPBOX_CONFIGURED = process.env['MAPBOX_CONFIGURED'] || false;
28
29const Header = (props: Props) => {
30 const {travel, toggleEditing} = props;
31 const {t} = useTranslation();
32 const {
33 userPermissions: {canEditTravel, canSeeTravelDetails, canSeeFullName},
34 } = usePermissions();
35 const setFocusOnTravel = useMapStore(s => s.setFocusOnTravel);
36 const {userId} = useProfile();
37 const isReturnEvent = useEventStore(s => s.event?.isReturnEvent);
38 const isUserTripCreator =
39 userId && userId === travel.attributes.user?.data?.id;
40
41 const passengersCount = travel?.attributes.passengers?.data.length || 0;
42 const availableSeats = travel?.attributes.seats - passengersCount || 0;
43
44 const tripHasValidCoordinates =
45 travel.attributes.meeting_latitude && travel.attributes.meeting_longitude;
46
47 return (
48 <Box
49 p={2}
50 onClick={() => {
51 setFocusOnTravel(travel);
52 const mapElement = document?.getElementById('map');
53 mapElement?.scrollIntoView({behavior: 'smooth'});
54 }}
55 >
56 {canEditTravel(travel) && (
57 <IconButton
58 size="small"
59 color="primary"
60 sx={{
61 position: 'absolute',
62 top: theme.spacing(1),
63 right: 0,
64 margin: theme.spacing(1),
65 }}
66 onClick={e => {
67 e.stopPropagation();
68 toggleEditing();
69 }}
70 id="EditTravelBtn"
71 >
72 <TuneIcon />
73 </IconButton>
74 )}
75 {!!travel.attributes.departureDate && (
76 <Typography
77 variant="overline"
78 sx={{color: 'GrayText', textTransform: 'capitalize'}}
79 id="TravelDeparture"
80 >
81 {moment(travel.attributes.departureDate).format('dddd LL')}{' '}
82 {travel.attributes.departureTime}
83 </Typography>
84 )}
85 <Typography variant="subtitle1">
86 {getTravelName(travel, canSeeFullName() || isUserTripCreator)}
87 {isUserTripCreator && (
88 <Typography component="span">
89 <Chip
90 sx={{mx: 1}}
91 label={t`generic.me`}
92 variant="outlined"
93 size="small"
94 />
95 </Typography>
96 )}
97 </Typography>
98
99 {!!travel.attributes.phone_number && canSeeTravelDetails(travel) && (
100 <Box sx={{marginTop: 2}}>
101 <Typography variant="overline" sx={{color: 'GrayText'}}>
102 {t('travel.fields.phone')}
103 </Typography>
104 <Typography variant="body1" id="TravelPhone">
105 {getFormatedPhoneNumber({
106 phone: travel.attributes.phone_number,
107 phoneCountry: travel.attributes.phoneCountry,
108 })}
109 </Typography>
110 </Box>
111 )}
112 {!!travel.attributes.meeting && (
113 <Box sx={{marginTop: 2}}>
114 <Typography variant="overline" sx={{color: 'GrayText'}}>
115 {t(isReturnEvent ? 'travel.destination' : 'travel.meeting')}
116 </Typography>
117 <Typography variant="body1">
118 <Link
119 component="a"
120 target="_blank"
121 rel="noopener noreferrer"
122 href={getMapsLink(travel.attributes.meeting)}
123 >
124 {travel.attributes.meeting}
125 </Link>
126 </Typography>
127 {MAPBOX_CONFIGURED && !tripHasValidCoordinates && (
128 <Typography
129 variant="overline"
130 color="warning.main"
131 >{t`placeInput.noCoordinates`}</Typography>
132 )}
133 </Box>
134 )}
135 {!!travel.attributes.details && (
136 <Box sx={{marginTop: 2}}>
137 <Typography variant="overline" sx={{color: 'GrayText'}}>
138 {t('travel.fields.details')}
139 </Typography>
140
141 <Typography
142 variant="body1"
143 sx={{whiteSpace: 'pre-line'}}
144 onClick={e => e.stopPropagation()}
145 >
146 <Linkify options={{render: DetailsLink}}>
147 {travel.attributes.details}
148 </Linkify>
149 </Typography>
150 </Box>
151 )}
152 <LinearProgress
153 sx={{
154 width: 1,
155 mt: 2,
156 mb: 1,
157 backgroundColor: 'LightGray',
158 '& .MuiLinearProgress-bar': {
159 backgroundColor: 'Gray',
160 },
161 }}
162 value={(passengersCount / travel?.attributes.seats) * 100}
163 variant="determinate"
164 />
165 <Box display="flex" justifyContent="space-between" sx={{width: 1}}>
166 <Typography variant="body1" sx={{color: 'GrayText'}}>
167 {t('passenger.assign.seats', {count: availableSeats})}
168 </Typography>
169 </Box>
170 </Box>
171 );
172};
173
174export default Header;