all repos — caroster @ 0d2ea7429f2b931b7cd6232d93efa930db9223d1

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

feat: đź’„ Put event details on its own page
Simon Mulquin simon@octree.ch
Fri, 19 Aug 2022 07:45:14 +0000
commit

0d2ea7429f2b931b7cd6232d93efa930db9223d1

parent

d2e47634bff1fe498b6b81f90a89ff0b5817c5c8

D frontend/components/CopyLink/index.tsx

@@ -1,40 +0,0 @@

-import Button, { ButtonProps } from '@material-ui/core/Button'; -import Icon from '@material-ui/core/Icon'; - -interface Props { - buttonText: string; - title: string; - url: string; - onShare: () => void; -} - -const CopyLink = ({buttonText, title, url, onShare, ...buttonProps}: ButtonProps & Props) => { - const share = async () => { - if (!url || !title) return null; - // If navigator share capability - if (!!navigator.share) - return await navigator.share({ - title, - url, - }); - // Else copy URL in clipboard - else if (!!navigator.clipboard) { - await navigator.clipboard.writeText(url); - onShare(); - return true; - } - }; - - return ( - <Button - variant="outlined" - startIcon={<Icon>share</Icon>} - onClick={share} - {...buttonProps} - > - {buttonText} - </Button> - ); -}; - -export default CopyLink;
M frontend/containers/DrawerMenu/index.tsxfrontend/containers/DrawerMenu/index.tsx

@@ -4,12 +4,9 @@ import {useTranslation} from 'react-i18next';

import {useRouter} from 'next/router'; import DrawerMenuItem from './DrawerMenuItem'; import useStyles from './styles'; -import useEventStore from '../../stores/useEventStore'; const DrawerMenu = () => { const {t} = useTranslation(); - const areDetailsOpened = useEventStore(s => s.areDetailsOpened); - const setAreDetailsOpened = useEventStore(s => s.setAreDetailsOpened); const classes = useStyles(); const router = useRouter(); const {

@@ -22,7 +19,6 @@ <DrawerMenuItem

title={t('drawer.travels')} onClick={() => { router.push(`/e/${uuid}`, null, {shallow: true}); - setAreDetailsOpened(false); }} Icon={<Icon>directions_car</Icon>} active={router.pathname == `/e/[uuid]`}

@@ -31,16 +27,17 @@ <DrawerMenuItem

title={t('drawer.waitingList')} onClick={() => { router.push(`/e/${uuid}/waitingList`, null, {shallow: true}); - setAreDetailsOpened(false); }} Icon={<Icon>group</Icon>} active={router.pathname == `/e/[uuid]/waitingList`} /> <DrawerMenuItem title={t('drawer.information')} - onClick={() => setAreDetailsOpened(true)} + onClick={() => { + router.push(`/e/${uuid}/details`, null, {shallow: true}); + }} Icon={<Icon>info</Icon>} - active={areDetailsOpened} + active={router.pathname == `/e/[uuid]/details`} /> </Drawer> );
M frontend/containers/EventBar/index.tsxfrontend/containers/EventBar/index.tsx

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

-import {useState} from 'react'; -import {useRouter} from 'next/router'; import Link from 'next/link'; -import {makeStyles} from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography';

@@ -9,24 +6,23 @@ import IconButton from '@material-ui/core/IconButton';

import Tooltip from '@material-ui/core/Tooltip'; import Avatar from '@material-ui/core/Avatar'; import Icon from '@material-ui/core/Icon'; +import {makeStyles} from '@material-ui/core/styles'; +import {useState} from 'react'; +import {useRouter} from 'next/router'; import {useTranslation} from 'react-i18next'; import useAuthStore from '../../stores/useAuthStore'; -import useEventStore from '../../stores/useEventStore'; import useProfile from '../../hooks/useProfile'; +import useShare from '../../hooks/useShare'; import GenericMenu from '../GenericMenu'; -import EventDetails from '../EventDetails'; -const EventBar = ({event, onAdd, onSave}) => { +const EventBar = ({event, onAdd}) => { const {t} = useTranslation(); const router = useRouter(); + const {share} = useShare(); const [anchorEl, setAnchorEl] = useState(null); - const isEditing = useEventStore(s => s.isEditing); - const areDetailsOpened = useEventStore(s => s.areDetailsOpened); - const setIsEditing = useEventStore(s => s.setIsEditing); - const setAreDetailsOpened = useEventStore(s => s.setAreDetailsOpened); const token = useAuthStore(s => s.token); const {user} = useProfile(); - const classes = useStyles({areDetailsOpened}); + const classes = useStyles(); const signUp = () => router.push({

@@ -93,9 +89,7 @@ <AppBar

className={classes.appbar} color="primary" position="static" - id={ - (isEditing && 'EditEvent') || (areDetailsOpened && 'Details') || 'Menu' - } + id="Menu" > <Toolbar> <div className={classes.name}>

@@ -116,85 +110,48 @@ >

{event.name} </Typography> </Tooltip> - - {areDetailsOpened && ( - <IconButton - className="tour_event_edit" - color="inherit" - edge="end" - id="HeaderAction" - onClick={isEditing ? onSave : () => setIsEditing(true)} - > - <Icon>{isEditing ? 'done' : 'edit'}</Icon> - </IconButton> - )} </div> - {areDetailsOpened ? ( + <> + <IconButton + className={classes.shareIcon} + color="inherit" + edge="end" + id="ShareBtn" + onClick={() => + share({ + title: `Caroster ${event.name}`, + url: `${window.location.href}`, + }) + } + > + <Icon>share</Icon> + </IconButton> + { + <GenericMenu + anchorEl={anchorEl} + setAnchorEl={setAnchorEl} + actions={[...userInfos, ...menuActions]} + /> + } <IconButton color="inherit" edge="end" - id="CloseDetailsBtn" - onClick={() => { - setIsEditing(false); - setAreDetailsOpened(!areDetailsOpened); - }} + id="MenuMoreInfo" + onClick={e => setAnchorEl(e.currentTarget)} > - <Icon>close</Icon> + {UserIcon} </IconButton> - ) : ( - <> - <IconButton - className={classes.shareIcon} - color="inherit" - edge="end" - id="ShareBtn" - onClick={() => setAreDetailsOpened(!areDetailsOpened)} - > - <Icon>share</Icon> - </IconButton> - <IconButton - color="inherit" - edge="end" - id="MenuMoreInfo" - onClick={e => setAnchorEl(e.currentTarget)} - > - {UserIcon} - </IconButton> - </> - )} - {!areDetailsOpened && ( - <GenericMenu - anchorEl={anchorEl} - setAnchorEl={setAnchorEl} - actions={[ - ...userInfos, - ...[ - { - label: areDetailsOpened - ? t('event.actions.hide_details') - : t('event.actions.show_details'), - onClick: e => { - setAnchorEl(null); - setAreDetailsOpened(!areDetailsOpened); - }, - id: 'DetailsTab', - }, - ], - ...menuActions, - ]} - /> - )} + </> </Toolbar> - {areDetailsOpened && <EventDetails />} </AppBar> ); }; const useStyles = makeStyles(theme => ({ - appbar: ({detailsOpen}) => ({ + appbar: () => ({ overflow: 'hidden', - minHeight: detailsOpen ? '100vh' : theme.mixins.toolbar.minHeight, - overflowY: detailsOpen ? 'scroll' : 'hidden', + minHeight: theme.mixins.toolbar.minHeight, + overflowY: 'hidden', transition: 'height 0.3s ease', }), logo: {
D frontend/containers/EventDetails/index.tsx

@@ -1,185 +0,0 @@

-import {useRef} from 'react'; -import {makeStyles, createMuiTheme, ThemeProvider} from '@material-ui/core'; -import Typography from '@material-ui/core/Typography'; -import TextField from '@material-ui/core/TextField'; -import Link from '@material-ui/core/Link'; -import Box from '@material-ui/core/Box'; -import {DatePicker} from '@material-ui/pickers'; -import {useTranslation} from 'react-i18next'; -import moment from 'moment'; -import useEventStore from '../../stores/useEventStore'; -import {caroster} from '../../theme'; -import CopyLink from '../../components/CopyLink'; -import useToastStore from '../../stores/useToastStore'; - -const EventDetails = () => { - const {t} = useTranslation(); - const event = useEventStore(s => s.event); - const addToast = useToastStore(s => s.addToast); - const setEventUpdate = useEventStore(s => s.setEventUpdate); - const isEditing = useEventStore(s => s.isEditing); - const shareInput = useRef(null); - const idPrefix = isEditing ? 'EditEvent' : 'Event'; - const classes = useStyles(); - - if (!event) return null; - - return ( - <ThemeProvider theme={theme}> - <Box className={classes.container}> - <div className={classes.section}> - {isEditing && ( - <div className={classes.section}> - <Typography variant="h6">{t('event.fields.name')}</Typography> - <TextField - fullWidth - value={event.name} - onChange={e => setEventUpdate({name: e.target.value})} - name="name" - id="EditEventName" - /> - </div> - )} - <Typography variant="h6">{t('event.fields.date')}</Typography> - {isEditing ? ( - <DatePicker - fullWidth - placeholder={t('event.fields.date_placeholder')} - value={event.date} - onChange={date => - setEventUpdate({ - date: !date ? null : moment(date).format('YYYY-MM-DD'), - }) - } - format="DD/MM/YYYY" - cancelLabel={t('generic.cancel')} - clearable - clearLabel={t('generic.clear')} - id={`${idPrefix}Date`} - /> - ) : ( - <Typography variant="body1" id={`${idPrefix}Date`}> - {event.date - ? moment(event.date).format('DD/MM/YYYY') - : t('event.fields.empty')} - </Typography> - )} - </div> - <div className={classes.section}> - <Typography variant="h6">{t('event.fields.address')}</Typography> - {isEditing ? ( - <TextField - fullWidth - multiline - rowsMax={4} - inputProps={{maxLength: 250}} - helperText={`${event.address?.length ?? 0}/250`} - defaultValue={event.address} - value={event.address} - onChange={e => setEventUpdate({address: e.target.value})} - id={`${idPrefix}Address`} - name="address" - /> - ) : ( - <Typography variant="body1" id={`${idPrefix}Address`}> - {event.address ? ( - <Link - target="_blank" - rel="noreferrer" - href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent( - event.address - )}`} - onClick={e => e.preventDefault} - > - {event.address} - </Link> - ) : ( - t('event.fields.empty') - )} - </Typography> - )} - </div> - <div className={classes.section}> - <Typography variant="h6">{t('event.fields.description')}</Typography> - {isEditing ? ( - <TextField - fullWidth - multiline - rowsMax={4} - inputProps={{maxLength: 250}} - helperText={`${event.description?.length || 0}/250`} - defaultValue={event.description} - value={event.description || ''} - onChange={e => setEventUpdate({description: e.target.value})} - id={`${idPrefix}Description`} - name="description" - /> - ) : ( - <Typography variant="body1" id={`${idPrefix}Description`}> - {event.description ?? t('event.fields.empty')} - </Typography> - )} - </div> - <Typography variant="h6">{t('event.fields.link')}</Typography> - <Typography>{t('event.fields.link_desc')}</Typography> - <TextField - value={window.location.href} - inputProps={{ - ref: shareInput, - }} - InputProps={{disableUnderline: true}} - onFocus={() => { - if (shareInput) shareInput.current.select(); - }} - fullWidth - readOnly - name="eventShareLink" - id="ShareLink" - /> - - <CopyLink - buttonText={t('event.fields.share')} - title={`Caroster ${event.name}`} - url={`${window.location.href}`} - onShare={() => { - addToast(t('event.actions.copied')); - }} - /> - </Box> - </ThemeProvider> - ); -}; - -const theme = createMuiTheme({ - ...caroster, - palette: { - ...caroster.palette, - type: 'dark', - }, -}); - -const useStyles = makeStyles(theme => ({ - container: () => ({ - padding: theme.spacing(2, 9), - marginBottom: theme.spacing(12), - minHeight: `calc(100vh - ${theme.mixins.toolbar.minHeight}px)`, - - [theme.breakpoints.down('xs')]: { - padding: theme.spacing(2), - minHeight: `calc(100vh - ${theme.mixins.toolbar.minHeight + 56}px)`, - }, - }), - section: { - marginBottom: theme.spacing(2), - width: '540px', - maxWidth: '100%', - }, - map: { - marginTop: theme.spacing(4), - }, - seeOnGMapButton: { - marginLeft: theme.spacing(2), - }, -})); - -export default EventDetails;
A frontend/containers/ShareEvent/index.tsx

@@ -0,0 +1,33 @@

+import Icon from '@material-ui/core/Icon'; +import Button, {ButtonProps} from '@material-ui/core/Button'; +import {useTranslation} from 'react-i18next'; +import useShare from '../../hooks/useShare'; + +interface Props { + title: string; + url: string; + className: string; +} + +const ShareEvent = ({title, url, className}: ButtonProps & Props) => { + const {t} = useTranslation(); + const {share, navigatorHasShareCapability} = useShare(); + + const text = navigatorHasShareCapability + ? t('event.fields.share') + : t('event.fields.copyLink'); + + return ( + <Button + variant="outlined" + color="primary" + startIcon={<Icon>share</Icon>} + onClick={() => share({title, url})} + className={className} + > + {text} + </Button> + ); +}; + +export default ShareEvent;
M frontend/containers/TravelColumns/NoCar.tsxfrontend/containers/TravelColumns/NoCar.tsx

@@ -2,8 +2,7 @@ import Typography from '@material-ui/core/Typography';

import Box from '@material-ui/core/Box'; import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; -import Copylink from '../../components/CopyLink'; -import useToastStore from '../../stores/useToastStore'; +import ShareEvent from '../ShareEvent'; interface Props { eventName: string;

@@ -16,20 +15,17 @@

const NoCar = ({eventName, title, image}: Props) => { const classes = useStyles({image}); const {t} = useTranslation(); - const addToast = useToastStore(s => s.addToast); return ( <Box className={classes.noTravel}> <Typography variant="h5">{title}</Typography> <img className={classes.noTravelImage} src="/assets/car.png" /> <Typography>{t('event.no_travel.desc')}</Typography> - <Copylink + <ShareEvent color="primary" className={classes.share} - buttonText={t('event.fields.share')} title={`Caroster ${eventName}`} url={`${url}`} - onShare={() => addToast(t('event.actions.copied'))} /> </Box> );
M frontend/containers/WaitingList/TravelDialog.tsxfrontend/containers/WaitingList/TravelDialog.tsx

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

-import {forwardRef} from 'react'; import moment from 'moment'; import Link from '@material-ui/core/Link'; import Typography from '@material-ui/core/Typography';

@@ -14,10 +13,10 @@ import Icon from '@material-ui/core/Icon';

import Box from '@material-ui/core/Box'; import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; -import {Passenger, Travel} from '../../generated/graphql'; +import {forwardRef} from 'react'; import getMapsLink from '../../utils/getMapsLink'; -import Copylink from '../../components/CopyLink'; -import useToastStore from '../../stores/useToastStore'; +import ShareEvent from '../ShareEvent'; +import {Passenger, Travel} from '../../generated/graphql'; interface Props { eventName: string;

@@ -38,7 +37,6 @@ onSelect,

}: Props) => { const classes = useStyles(); const {t} = useTranslation(); - const addToast = useToastStore(s => s.addToast); const availableTravels = travels?.filter( travel => travel.passengers && travel?.seats > travel.passengers.length

@@ -70,13 +68,11 @@ <img className={classes.noTravelImage} src="/assets/car.png" />

<Typography> {t('passenger.creation.no_travel.desc', {name: passenger?.name})} </Typography> - <Copylink + <ShareEvent color="primary" className={classes.share} - buttonText={t('event.fields.share')} title={`Caroster ${eventName}`} url={`${window.location.href}`} - onShare={() => addToast(t('event.actions.copied'))} /> </Box> )) || (
A frontend/hooks/useShare.ts

@@ -0,0 +1,33 @@

+import {useTranslation} from 'react-i18next'; +import useToastStore from '../stores/useToastStore'; + +const navigatorHasShareCapability = typeof navigator !== 'undefined' && !!navigator.share; +const navigatorHasClipboardCapability = typeof navigator !== 'undefined' && !!navigator.clipboard; + + +const useShare = () => { + const {t} = useTranslation(); + const addToast = useToastStore(s => s.addToast); + + return { + navigatorHasShareCapability, + share: async ({url, title}) => { + if (!url || !title) return null; + // If navigator share capability + if (navigatorHasShareCapability) + return await navigator.share({ + title, + url, + }); + // Else copy URL in clipboard + else if (navigatorHasClipboardCapability) { + await navigator.clipboard.writeText(url); + addToast(t('event.actions.copied')); + return true; + } + addToast(t('event.actions.noShareCapability')); + }, + }; +}; + +export default useShare;
M frontend/layouts/Event.tsxfrontend/layouts/Event.tsx

@@ -32,11 +32,7 @@ const {t} = useTranslation();

const theme = useTheme(); const classes = useStyles(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const addToast = useToastStore(s => s.addToast); const setEvent = useEventStore(s => s.setEvent); - const eventUpdate = useEventStore(s => s.event); - const setIsEditing = useEventStore(s => s.setIsEditing); - const [updateEvent] = useUpdateEventMutation(); const [isAddToMyEvent, setIsAddToMyEvent] = useState(false); const {data: {eventByUUID: event} = {}} = useEventByUuidQuery({ pollInterval: POLL_INTERVAL,

@@ -49,29 +45,12 @@ }, [event]);

if (!event) return <ErrorPage statusCode={404} title={t`event.not_found`} />; - const onSave = async e => { - try { - const {uuid, ...data} = eventUpdate; - const {id, __typename, travels, users, waitingList, ...input} = data; - await updateEvent({ - variables: {uuid, eventUpdate: input as EditEventInput}, - refetchQueries: ['eventByUUID'], - }); - setIsEditing(false); - } catch (error) { - console.error(error); - addToast(t('event.errors.cant_update')); - } - }; - return ( <Layout pageTitle={t('event.title', {title: event.name})} menuTitle={t('event.title', {title: event.name})} displayMenu={false} - Topbar={() => ( - <EventBar event={event} onAdd={setIsAddToMyEvent} onSave={onSave} /> - )} + Topbar={() => <EventBar event={event} onAdd={setIsAddToMyEvent} />} > <Box flex={1}
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -75,6 +75,9 @@ "not_found": "Project not found",

"no_travel.title": "There are currently no cars", "no_other_travel.title": "There are currently no other car", "no_travel.desc": "Share the event and register to the waiting list to get notified when a car will be available!", + "details.modify": "Modify", + "details.save": "Save", + "details.aboutCaroster": "Learn more about Caroster", "fields": { "name": "Name of the event", "date": "Event date",

@@ -83,8 +86,9 @@ "description": "Description",

"address": "Event address", "empty": "Not specified", "link": "Share link", - "link_desc": "Share this link to invite people to carpool", - "share": "Copy link" + "link_desc": "Share this link with other people", + "share": "Share", + "copyLink": "Copy link" }, "creation": { "title": "New event",

@@ -111,10 +115,9 @@ }

} }, "actions": { - "show_details": "Event details", - "hide_details": "Hide details", "find_car": "Find a car", "copied": "The link has been copied to your clipboard", + "noShareCapability": "Your browser cannot share nor copy to clipboard, please copy the page's URL instead.", "add_to_my_events": "Add to my events", "see_on_gmap": "See on a map" },
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -75,6 +75,9 @@ "not_found": "Projet introuvable",

"no_travel.title": "Pas de voitures pour le moment", "no_other_travel.title": "Pas d'autres voitures pour le moment", "no_travel.desc": "Partagez l’événement et inscrivez-vous dans la liste d’attente pour être notifié lorsqu’une voiture sera ajoutée !", + "details.modify": "Modifier", + "details.save": "Enregistrer", + "details.aboutCaroster": "En savoir plus sur Caroster", "fields": { "name": "Nom de l'événement", "date": "Date de l'événement",

@@ -83,8 +86,9 @@ "description": "Description",

"address": "Adresse de l'événement", "empty": "Non précisé", "link": "Lien de partage", - "link_desc": "Partager ce lien pour inviter les gens à faire du covoiturage", - "share": "Copier le lien" + "link_desc": "Partager l'évènement à d'autres personnes", + "share": "Partager", + "copyLink": "Copier le lien" }, "creation": { "title": "Nouvel évènement",

@@ -111,10 +115,9 @@ }

} }, "actions": { - "show_details": "Détails de l'événement", - "hide_details": "Cacher les détails", "find_car": "Trouver une voiture", "copied": "Le lien a été copié dans votre presse-papier", + "noShareCapability": "Votre navigateur ne permet pas de partager ou de copier dans le presse papier, veuillez copier l'URL de la page.", "add_to_my_events": "Ajouter à mes évènements", "see_on_gmap": "Voir sur une carte" },
A frontend/pages/e/[uuid]/details.tsx

@@ -0,0 +1,262 @@

+import moment from 'moment'; +import Button from '@material-ui/core/Button'; +import Box from '@material-ui/core/Box'; +import Link from '@material-ui/core/Link'; +import Paper from '@material-ui/core/Paper'; +import Divider from '@material-ui/core/Divider'; +import Container from '@material-ui/core/Container'; +import TextField from '@material-ui/core/TextField'; +import Typography from '@material-ui/core/Typography'; +import {makeStyles} from '@material-ui/core/styles'; +import {DatePicker} from '@material-ui/pickers'; +import {PropsWithChildren, useState} from 'react'; +import {useTranslation} from 'react-i18next'; +import ShareEvent from '../../../containers/ShareEvent'; +import useEventStore from '../../../stores/useEventStore'; +import useToastStore from '../../../stores/useToastStore'; +import useSettings from '../../../hooks/useSettings'; +import EventLayout, {TabComponent} from '../../../layouts/Event'; +import {initializeApollo} from '../../../lib/apolloClient'; +import { + EventByUuidDocument, + useUpdateEventMutation, +} from '../../../generated/graphql'; + +interface Props { + eventUUID: string; +} + +const Page = (props: PropsWithChildren<Props>) => { + return <EventLayout {...props} Tab={DetailsTab} />; +}; + +const DetailsTab: TabComponent = ({}) => { + const {t} = useTranslation(); + const settings = useSettings(); + const [updateEvent] = useUpdateEventMutation(); + const addToast = useToastStore(s => s.addToast); + const setEventUpdate = useEventStore(s => s.setEventUpdate); + const event = useEventStore(s => s.event); + const [isEditing, setIsEditing] = useState(false); + const idPrefix = isEditing ? 'EditEvent' : 'Event'; + const classes = useStyles(); + + const onSave = async e => { + try { + const {uuid, ...data} = event; + const {id, __typename, travels, users, waitingList, ...input} = data; + await updateEvent({ + variables: {uuid, eventUpdate: input}, + refetchQueries: ['eventByUUID'], + }); + setIsEditing(false); + } catch (error) { + console.error(error); + addToast(t('event.errors.cant_update')); + } + }; + + const modifyButton = isEditing ? ( + <Button + variant="contained" + color="primary" + className={classes.modify} + onClick={onSave} + > + {t('event.details.save')} + </Button> + ) : ( + <Button + variant="text" + color="primary" + className={classes.modify} + onClick={() => setIsEditing(true)} + > + {t('event.details.modify')} + </Button> + ); + + if (!event) return null; + + return ( + <Box className={classes.root}> + <Container maxWidth="sm" className={classes.card}> + <Paper className={classes.paper}> + {modifyButton} + <div className={classes.section}> + <Typography variant="h6">{t('event.fields.name')}</Typography> + {isEditing ? ( + <TextField + fullWidth + value={event.name} + onChange={e => setEventUpdate({name: e.target.value})} + name="name" + id="EditEventName" + /> + ) : ( + <Typography variant="body1" id={`${idPrefix}Name`}> + {event.name ?? t('event.fields.empty')} + </Typography> + )} + </div> + <div className={classes.section}> + <Typography variant="h6">{t('event.fields.date')}</Typography> + {isEditing ? ( + <DatePicker + fullWidth + placeholder={t('event.fields.date_placeholder')} + value={event.date} + onChange={date => + setEventUpdate({ + date: !date ? null : moment(date).format('YYYY-MM-DD'), + }) + } + format="DD/MM/YYYY" + cancelLabel={t('generic.cancel')} + clearable + clearLabel={t('generic.clear')} + id={`${idPrefix}Date`} + /> + ) : ( + <Typography variant="body1" id={`${idPrefix}Date`}> + {event.date + ? moment(event.date).format('DD/MM/YYYY') + : t('event.fields.empty')} + </Typography> + )} + </div> + <div className={classes.section}> + <Typography variant="h6">{t('event.fields.address')}</Typography> + {isEditing ? ( + <TextField + fullWidth + multiline + rowsMax={4} + inputProps={{maxLength: 250}} + helperText={`${event.address?.length ?? 0}/250`} + defaultValue={event.address} + value={event.address} + onChange={e => setEventUpdate({address: e.target.value})} + id={`${idPrefix}Address`} + name="address" + /> + ) : ( + <Typography variant="body1" id={`${idPrefix}Address`}> + {event.address ? ( + <Link + target="_blank" + rel="noreferrer" + href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent( + event.address + )}`} + onClick={e => e.preventDefault} + > + {event.address} + </Link> + ) : ( + t('event.fields.empty') + )} + </Typography> + )} + </div> + <div className={classes.section}> + <Typography variant="h6"> + {t('event.fields.description')} + </Typography> + {isEditing ? ( + <TextField + fullWidth + multiline + rowsMax={4} + inputProps={{maxLength: 250}} + helperText={`${event.description?.length || 0}/250`} + defaultValue={event.description} + value={event.description || ''} + onChange={e => setEventUpdate({description: e.target.value})} + id={`${idPrefix}Description`} + name="description" + /> + ) : ( + <Typography variant="body1" id={`${idPrefix}Description`}> + {event.description ?? t('event.fields.empty')} + </Typography> + )} + </div> + <Typography variant="h6">{t('event.fields.link')}</Typography> + <Typography>{t('event.fields.link_desc')}</Typography> + <Box py={4} justifyContent="center" display="flex"> + <ShareEvent + title={`Caroster ${event.name}`} + url={`${window.location.href}`} + />{' '} + </Box> + <Divider variant="middle" /> + <Box pt={2} justifyContent="center" display="flex"> + <Link href={settings?.about_link} target="_blank" rel="noopener"> + {t('event.details.aboutCaroster')} + </Link> + </Box> + </Paper> + </Container> + </Box> + ); +}; + +const useStyles = makeStyles(theme => ({ + root: { + position: 'relative', + paddingLeft: '80px', + + [theme.breakpoints.down('sm')]: { + paddingLeft: 0, + paddingBottom: '80px', + }, + }, + paper: { + position: 'relative', + padding: theme.spacing(2), + }, + card: { + marginTop: theme.spacing(6), + }, + modify: { + position: 'absolute', + right: theme.spacing(2), + }, + section: { + marginBottom: theme.spacing(2), + width: '540px', + maxWidth: '100%', + paddingX: theme.spacing(2), + }, + map: { + marginTop: theme.spacing(4), + }, + seeOnGMapButton: { + marginLeft: theme.spacing(2), + }, +})); + +export async function getServerSideProps(ctx) { + const {uuid} = ctx.query; + const apolloClient = initializeApollo(); + const {data = {}} = await apolloClient.query({ + query: EventByUuidDocument, + variables: {uuid}, + }); + const {eventByUUID: event} = data; + const {host = ''} = ctx.req.headers; + + return { + props: { + event, + eventUUID: uuid, + metas: { + title: event?.name || '', + url: `https://${host}${ctx.resolvedUrl}`, + }, + }, + }; +} + +export default Page;
M frontend/stores/useEventStore.tsfrontend/stores/useEventStore.ts

@@ -4,11 +4,7 @@

type State = { event: Event; setEvent: (event: Event) => void; - setEventUpdate: (event: Event) => void; - areDetailsOpened: boolean; - setAreDetailsOpened: (areDetailsOpened: boolean) => void; - isEditing: boolean; - setIsEditing: (isEditing: boolean) => void; + setEventUpdate: (event: Partial<Event>) => void; }; const useEventStore = create<State>((set, get) => ({

@@ -18,15 +14,6 @@ setEventUpdate: eventUpdate => {

const event = get().event; set({event: {...event, ...eventUpdate}}); }, - areDetailsOpened: false, - setAreDetailsOpened: areDetailsOpened => { - if (!areDetailsOpened) { - set({areDetailsOpened, isEditing: false}); - } - set({areDetailsOpened}); - }, - isEditing: false, - setIsEditing: isEditing => set({isEditing}), })); export default useEventStore;