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