all repos — caroster @ 1b873bd29e3743cca69f0237124963dae6cfeab9

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

feat: :sparkles: Show login form on anonymous action for Caroster Plus event

#560
Tim Izzo tim@octree.ch
Fri, 20 Dec 2024 11:14:03 +0100
commit

1b873bd29e3743cca69f0237124963dae6cfeab9

parent

27332dc9778a133a03349f1ebc56af222fff16f0

M frontend/containers/AddPassengerButtons/index.tsxfrontend/containers/AddPassengerButtons/index.tsx

@@ -1,7 +1,11 @@

-import Box from '@mui/material/Box'; +import {Stack} from '@mui/material'; import Button from '@mui/material/Button'; import {useTranslation} from 'next-i18next'; -import usePermissions from '../../hooks/usePermissions'; +import useEventStore from '../../stores/useEventStore'; +import {useSession} from 'next-auth/react'; +import {useEffect, useReducer} from 'react'; +import LoginDialog from '../LoginDialog'; +import {useRouter} from 'next/router'; interface Props { onAddSelf: () => void;

@@ -16,42 +20,50 @@ waitingList: 'travel.passengers.add_to_waitingList',

travel: 'travel.passengers.add_to_travel', }; -const AddPassengerButtons = ({ - onAddSelf, - onAddOther, - registered, - variant, - disabled, -}: Props) => { +const AddPassengerButtons = (props: Props) => { + const {onAddSelf, onAddOther, registered, variant, disabled} = props; const {t} = useTranslation(); - const { - userPermissions: {canJoinTravels, canAddToTravel}, - } = usePermissions(); + const router = useRouter(); + const event = useEventStore(s => s.event); + const isCarosterPlus = event?.enabled_modules?.includes('caroster-plus'); + const session = useSession(); + const isAuthenticated = session.status === 'authenticated'; + const [showLoginDialog, toggleLoginDialog] = useReducer(i => !i, false); + + const onClickAddSelf = () => { + if (isCarosterPlus && !isAuthenticated) toggleLoginDialog(); + else onAddSelf(); + }; + + useEffect(() => { + if (router.query.action === 'addSelf') { + onAddSelf(); + router.replace( + {pathname: `/${router.locale}/e/${router.query.uuid}`, query: null}, + undefined, + {shallow: true} + ); + } + }, [router, onAddSelf]); return ( - <Box textAlign="center"> - {canJoinTravels() && ( - <Box p={1} pt={2}> - <Button - sx={buttonStyle} - variant="contained" - color="primary" - fullWidth - onClick={onAddSelf} - disabled={disabled || registered} - > - {t( - registered - ? 'travel.passengers.registered' - : 'travel.passengers.add_me' - )} - </Button> - </Box> - )} - {canAddToTravel() && ( - <Box p={1} pt={2}> + <> + <Stack spacing={2} p={1} pt={2}> + <Button + variant="contained" + color="primary" + fullWidth + onClick={onClickAddSelf} + disabled={disabled || registered} + > + {t( + registered + ? 'travel.passengers.registered' + : 'travel.passengers.add_me' + )} + </Button> + {isCarosterPlus && isAuthenticated && ( <Button - sx={buttonStyle} variant="outlined" color="primary" fullWidth

@@ -60,18 +72,17 @@ disabled={disabled}

> {t(ADD_TO_LOCALE[variant])} </Button> - </Box> - )} - </Box> + )} + </Stack> + <LoginDialog + title={t`travel.passengers.add_me`} + content={t`travel.passengers.add_me.loginNotice`} + open={showLoginDialog} + toggle={toggleLoginDialog} + redirectPath={`/e/${event?.uuid}/?action=addSelf`} + /> + </> ); -}; - -const buttonStyle = { - py: 1, - px: 8, - md: { - px: 4, - }, }; export default AddPassengerButtons;
A frontend/containers/AddTravelButton/index.tsx

@@ -0,0 +1,68 @@

+import {Button, Icon} from '@mui/material'; +import {useEffect, useReducer} from 'react'; +import {useTranslation} from 'react-i18next'; +import NewTravelDialog from './NewTravelDialog'; +import useEventStore from '../../stores/useEventStore'; +import {useSession} from 'next-auth/react'; +import {useRouter} from 'next/router'; +import LoginDialog from '../LoginDialog'; + +type Props = {}; + +const AddTravelButton = (props: Props) => { + const {t} = useTranslation(); + const router = useRouter(); + const event = useEventStore(s => s.event); + const isCarosterPlus = event?.enabled_modules?.includes('caroster-plus'); + const session = useSession(); + const isAuthenticated = session.status === 'authenticated'; + const [showLoginDialog, toggleLoginDialog] = useReducer(i => !i, false); + const [showNewTravelDialog, toggleNewTravelDialog] = useReducer( + i => !i, + false + ); + + const onClick = () => { + if (isCarosterPlus && !isAuthenticated) toggleLoginDialog(); + else toggleNewTravelDialog(); + }; + + useEffect(() => { + if (router.query.action === 'createTravel') { + toggleNewTravelDialog(); + router.replace( + {pathname: `/${router.locale}/e/${router.query.uuid}`, query: null}, + undefined, + {shallow: true} + ); + } + }, [router]); + + return ( + <> + <Button + aria-label="add-car" + variant="contained" + color="secondary" + endIcon={<Icon>add</Icon>} + sx={{width: {xs: 1, sm: 'auto'}}} + onClick={onClick} + > + {t('travel.creation.title')} + </Button> + <NewTravelDialog + open={showNewTravelDialog} + toggle={toggleNewTravelDialog} + /> + <LoginDialog + title={t`travel.creation.title`} + content={t`travel.creation.loginNotice`} + open={showLoginDialog} + toggle={toggleLoginDialog} + redirectPath={`/e/${event?.uuid}/?action=createTravel`} + /> + </> + ); +}; + +export default AddTravelButton;
A frontend/containers/LoginDialog/index.tsx

@@ -0,0 +1,68 @@

+import { + Button, + Dialog, + DialogContent, + DialogTitle, + Typography, +} from '@mui/material'; +import {useTranslation} from 'react-i18next'; +import LoginForm from '../LoginForm'; +import {useState} from 'react'; +import {setCookie} from '../../lib/cookies'; + +type Props = { + open: boolean; + toggle: () => void; + redirectPath: string; + title?: string; + content?: string; +}; + +const LoginDialog = (props: Props) => { + const {t} = useTranslation(); + const { + open, + toggle, + redirectPath, + title = t`signin.title`, + content = '', + } = props; + const [sent, setSent] = useState(false); + + const onSend = () => { + setSent(true); + setCookie('redirectPath', redirectPath); + }; + + return ( + <Dialog open={open} onClose={toggle} maxWidth="xs"> + <DialogTitle>{title}</DialogTitle> + <DialogContent> + {!sent && ( + <> + {content && <Typography mb={2}>{content}</Typography>} + <LoginForm onSend={onSend} /> + <Button + fullWidth + sx={{mt: 2}} + variant="outlined" + onClick={toggle} + >{t`generic.cancel`}</Button> + </> + )} + {sent && ( + <> + <Typography mb={2}>{t`signin.check_email`}</Typography> + <Button + fullWidth + variant="contained" + onClick={toggle} + >{t`generic.close`}</Button> + </> + )} + </DialogContent> + </Dialog> + ); +}; + +export default LoginDialog;
A frontend/containers/LoginForm/index.tsx

@@ -0,0 +1,89 @@

+import { + Button, + FormHelperText, + Stack, + TextField, + Typography, +} from '@mui/material'; +import LoginGoogle from '../LoginGoogle'; +import {useTranslation} from 'react-i18next'; +import {useState} from 'react'; +import {useSendMagicLinkMutation} from '../../generated/graphql'; + +type Props = { + error?: string; + showGoogleAuth?: boolean; + onSend?: () => void; +}; + +const LoginForm = (props: Props) => { + const {error, showGoogleAuth, onSend} = props; + const {t, i18n} = useTranslation(); + const [email, setEmail] = useState(''); + const [sent, setSent] = useState(false); + const [magicLinkError, setMagicLinkError] = useState<string>(null); + const [sendMagicLink] = useSendMagicLinkMutation(); + + const handleSubmit = async () => { + try { + setMagicLinkError(null); + if (email) await sendMagicLink({variables: {email, lang: i18n.language}}); + setSent(true); + onSend?.(); + } catch (error) { + console.error(error); + if (error.message === 'GoogleAccount') setMagicLinkError(error.message); + } + }; + + return ( + <Stack spacing={2}> + {(error || magicLinkError) && ( + <FormHelperText error sx={{textAlign: 'center'}}> + {t(errorsMap[error || magicLinkError])} + </FormHelperText> + )} + {!sent && ( + <> + <TextField + label={t`signin.email`} + fullWidth + required + value={email} + onChange={e => setEmail(e.target.value)} + type="email" + /> + <Button + fullWidth + color="primary" + variant="contained" + disabled={!email} + onClick={handleSubmit} + > + {t('signin.sendLink')} + </Button> + {showGoogleAuth && ( + <> + <Typography align="center">{t('signin.or')}</Typography> + <LoginGoogle /> + </> + )} + </> + )} + {sent && ( + <Typography + variant="body2" + align="center" + pt={2} + >{t`signin.check_email`}</Typography> + )} + </Stack> + ); +}; + +const errorsMap = { + CredentialsSignin: 'signin.errors.CredentialsSignin', + GoogleAccount: 'signin.errors.GoogleAccount', +}; + +export default LoginForm;
M frontend/containers/NewTravelDialog/index.tsxfrontend/containers/AddTravelButton/NewTravelDialog.tsx

@@ -21,11 +21,11 @@ import FAQLink from './FAQLink';

import {useSession} from 'next-auth/react'; interface Props { - opened: boolean; - toggle: (opts: {opened: boolean}) => void; + open: boolean; + toggle: () => void; } -const NewTravelDialog = ({opened, toggle}: Props) => { +const NewTravelDialog = ({open, toggle}: Props) => { const {t} = useTranslation(); const event = useEventStore(s => s.event); const {createTravel} = useActions({event});

@@ -85,7 +85,7 @@ event: event.id,

}; await createTravel(travel); - toggle({opened: false}); + toggle(); clearState(); };

@@ -94,9 +94,9 @@ return (

<Dialog fullWidth maxWidth="xs" - open={opened} + open={open} onClose={() => { - toggle({opened: false}); + toggle(); clearState(); }} TransitionComponent={Transition}

@@ -250,7 +250,7 @@ >

<Button color="primary" id="NewTravelCancel" - onClick={() => toggle({opened: false})} + onClick={toggle} tabIndex={-1} > {t('generic.cancel')}
M frontend/containers/Travel/index.tsxfrontend/containers/Travel/index.tsx

@@ -27,7 +27,7 @@ const isCarosterPlus = useEventStore(s =>

s.event.enabled_modules?.includes('caroster-plus') ); const { - userPermissions: {canSeePassengerDetails, canJoinTravels, canAddToTravel}, + userPermissions: {canSeePassengerDetails}, } = usePermissions(); const theme = useTheme(); const [isEditing, toggleEditing] = useReducer(i => !i, false);

@@ -75,20 +75,18 @@ )}

{!isEditing && ( <> <Header travel={travel} toggleEditing={toggleEditing} /> - {(canJoinTravels() || canAddToTravel()) && ( - <> - <Divider /> - <AddPassengerButtons - registered={registered} - variant="travel" - disabled={disableNewPassengers} - onAddOther={props.onAddOther} - onAddSelf={ - isCarosterPlus ? toggleRequestTripModal : props.onAddSelf - } - /> - </> - )} + <> + <Divider /> + <AddPassengerButtons + registered={registered} + variant="travel" + disabled={disableNewPassengers} + onAddOther={props.onAddOther} + onAddSelf={ + isCarosterPlus ? toggleRequestTripModal : props.onAddSelf + } + /> + </> {travel.attributes.passengers.data.length > 0 && <Divider />} <PassengersList passengers={travel.attributes.passengers.data}
M frontend/containers/TravelColumns/NoCar.tsxfrontend/containers/TravelColumns/NoCar.tsx

@@ -6,53 +6,45 @@ import {useRouter} from 'next/router';

import Box from '@mui/material/Box'; import ShareEvent from '../ShareEvent'; import SupportCaroster from '../SupportCaroster'; -import {Icon} from '@mui/material'; -import usePermissions from '../../hooks/usePermissions'; +import AddTravelButton from '../AddTravelButton'; interface Props { eventName: string; title: string; isCarosterPlus: string; - showImage?: boolean; - showTravelModal: () => void; + noTravel?: boolean; } -const NoCar = ({ - eventName, - title, - isCarosterPlus, - showImage, - showTravelModal, -}: Props) => { +const NoCar = ({eventName, title, isCarosterPlus, noTravel}: Props) => { const {t} = useTranslation(); const theme = useTheme(); const router = useRouter(); const {uuid} = router.query; - const { - userPermissions: {canAddTravel}, - } = usePermissions(); return ( - <Box my={4} mx="auto" pb={16} mt={15} maxWidth="100%" width={340}> + <Box + my={4} + mx="auto" + pb={16} + mt={noTravel ? 15 : 4} + maxWidth="100%" + width={340} + > <Typography variant="h6" align="center" color="textSecondary"> {title} </Typography> - {showImage && ( + {noTravel && ( <> - {canAddTravel() && ( - <Box my={4} textAlign="center"> - <Button - onClick={showTravelModal} - aria-label="add-car" - variant="contained" - color="secondary" - endIcon={<Icon>add</Icon>} - > - {t('travel.creation.title')} - </Button> - </Box> - )} + <Box + display="flex" + justifyContent="center" + mt={2} + maxWidth={200} + mx="auto" + > + <AddTravelButton /> + </Box> <Box component="img" sx={{
M frontend/containers/TravelColumns/index.tsxfrontend/containers/TravelColumns/index.tsx

@@ -15,17 +15,14 @@ import NoCar from './NoCar';

import {TravelEntity} from '../../generated/graphql'; import {AddPassengerToTravel} from '../NewPassengerDialog'; import MasonryContainer from './MasonryContainer'; -import LoginToAttend from '../LoginToAttend/LoginToAttend'; -import usePermissions from '../../hooks/usePermissions'; import useDisplayTravels from './useDisplayTravels'; import useDisplayMarkers from './useDisplayMarkers'; import FilterByDate from './FilterByDate'; import {Button, Icon, useMediaQuery} from '@mui/material'; import useTravelsStore from '../../stores/useTravelsStore'; +import AddTravelButton from '../AddTravelButton'; -interface Props { - showTravelModal: () => void; -} +interface Props {} const TravelColumns = (props: Props) => { const theme = useTheme();

@@ -35,9 +32,6 @@ const {t} = useTranslation();

const addToast = useToastStore(s => s.addToast); const {addToEvent} = useAddToEvents(); const {profile, userId} = useProfile(); - const { - userPermissions: {canAddTravel}, - } = usePermissions(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();

@@ -85,8 +79,7 @@

if (!event || travels?.length === 0) return ( <NoCar - showImage - showTravelModal={props.showTravelModal} + noTravel eventName={event?.name} title={t('event.no_travel.title')} isCarosterPlus={isCarosterPlus}

@@ -113,18 +106,7 @@ maxWidth="100%"

flexWrap="wrap" > <FilterByDate dates={dates} buttonFilterContent={buttonFilterContent} /> - {canAddTravel() && ( - <Button - onClick={props.showTravelModal} - aria-label="add-car" - variant="contained" - color="secondary" - endIcon={<Icon>add</Icon>} - sx={{width: {xs: 1, sm: 'auto'}}} - > - {t('travel.creation.title')} - </Button> - )} + <AddTravelButton /> {haveGeopoints && ( <Button sx={{width: {xs: 1, sm: 'auto'}}}

@@ -150,11 +132,6 @@ },

}} > <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}> - {!canAddTravel() && ( - <MasonryContainer key="no_other_travel"> - <LoginToAttend title={t('event.loginToAttend')} /> - </MasonryContainer> - )} {displayedTravels?.map(travel => { return ( <MasonryContainer key={travel.id}>
M frontend/hooks/usePermissions.tsfrontend/hooks/usePermissions.ts

@@ -8,11 +8,8 @@ canEditEventDetails: () => boolean;

canEditWaitingList: () => boolean; canSeeAdminWaitingList: () => boolean; canSetAlert: () => boolean; - canAddTravel: () => boolean; canEditTravel: (travel: TravelEntity) => boolean; canSeeTravelDetails: (travel: TravelEntity) => boolean; - canJoinTravels: () => boolean; - canAddToTravel: () => boolean; canDeletePassenger: (passenger: PassengerEntity) => boolean; canSeePassengerDetails: (passenger: PassengerEntity) => boolean; canSeeFullName: () => boolean;

@@ -24,11 +21,8 @@ canEditEventDetails: () => false,

canEditWaitingList: () => false, canSeeAdminWaitingList: () => false, canSetAlert: () => false, - canAddTravel: () => false, canEditTravel: () => false, canSeeTravelDetails: () => false, - canJoinTravels: () => false, - canAddToTravel: () => false, canDeletePassenger: () => false, canSeePassengerDetails: () => false, canSeeFullName: () => false,

@@ -50,11 +44,8 @@ canEditEventDetails: () => true,

canEditWaitingList: () => true, canSeeAdminWaitingList: () => true, canSetAlert: () => true, - canAddTravel: () => true, canSeeTravelDetails: () => true, canEditTravel: () => true, - canJoinTravels: () => true, - canAddToTravel: () => true, canDeletePassenger: () => true, canSeePassengerDetails: () => true, canSeeFullName: () => userIsEventAdmin,

@@ -64,7 +55,7 @@ if (carosterPlus) {

if (userIsAnonymous) return {userPermissions: noPermissions}; else if (userIsEventAdmin) return { - userPermissions: {...allPermissions, canAddToTravel: () => false}, + userPermissions: allPermissions, }; else { const carosterPlusPermissions: UserPermissions = {

@@ -75,8 +66,6 @@ travel.attributes.user?.data?.id || travel.attributes.user;

return travelCreatorId === userId; }, - canJoinTravels: () => true, - canAddTravel: () => true, canSeeTravelDetails: travel => { const travelCreatorId = travel.attributes.user?.data?.id || travel.attributes.user;

@@ -118,7 +107,6 @@ canSeePassengerDetails: () => false,

canDeletePassenger: () => true, canEditEventOptions: () => userIsEventCreator, canSetAlert: () => false, - canJoinTravels: () => connected, canSeeTravelDetails: () => true, }, };
M frontend/locales/de.jsonfrontend/locales/de.json

@@ -159,7 +159,6 @@ "profile.stripe_link.button": "Verlauf",

"profile.title": "Profil", "signin.emailConfirmation": "Ihr Konto wurde bestätigt. Sie können sich jetzt anmelden.", "signin.login": "$t(menu.login)", - "signin.no_account": "Sie haben noch kein Konto ?", "signin.or": "oder", "signin.title": "Anmelden", "signin.withGoogle": "Google-Konto verwenden",
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -101,6 +101,7 @@ "generic.me": "Me",

"generic.remove": "Remove", "generic.save": "Save", "generic.select": "Select", + "generic.close": "Fermer", "menu.about": "Discover more about Caroster", "menu.code": "Caroster is Open Source", "menu.dashboard": "My Carosters",

@@ -201,7 +202,7 @@ "signin.emailConfirmation": "Your account has been confirmed. You can now login.",

"signin.errors.CredentialsSignin": "Login link has expired. Please enter your email to receive a new link.", "signin.errors.GoogleAccount": "This email address is linked to Google. Please use sign-in with Google.", "signin.login": "$t(menu.login)", - "signin.no_account": "You don't have an account ?", + "signin.sendLink": "Send login link", "signin.check_email": "A login link has been sent to your email. Please check your email to complete the login.", "signin.or": "OR", "signin.password": "Password",

@@ -235,6 +236,7 @@ "travel.creation.travel.dateHelper": "Please provide a valid date",

"travel.creation.travel.timeHelper": "Please provide a valid time", "travel.creation.travel.title": "Trip", "travel.creation.travel.titleHelper": "Please provide a valid name", + "travel.creation.loginNotice": "Enter your email to add a trip. You will receive a connection link at the address provided.", "travel.errors.cant_create": "Unable to create the trip", "travel.errors.cant_remove": "Unable to remove the trip", "travel.errors.cant_remove_passenger": "Unable to remove passenger",

@@ -245,6 +247,7 @@ "travel.destination": "Destination",

"travel.fields.phone": "Contact", "travel.passengers.add": "Add a passenger", "travel.passengers.add_me": "Add myself", + "travel.passengers.add_me.loginNotice": "Enter your email to add yourself to the trip. You will receive a connection link at the address provided.", "travel.passengers.add_someone": "Add someone", "travel.passengers.add_to_car": "Add to trip", "travel.passengers.add_to_travel": "Add passenger",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -98,6 +98,7 @@ "generic.remove": "Supprimer",

"generic.save": "Enregistrer", "generic.select": "Selectionner", "generic.optional": "Optionnel", + "generic.close": "Fermer", "menu.about": "En savoir plus sur Caroster", "menu.code": "Caroster est Open Source", "menu.dashboard": "Mes Carosters",

@@ -229,6 +230,7 @@ "travel.creation.travel.dateHelper": "Veuillez indiquer une date valide",

"travel.creation.travel.timeHelper": "Veuillez indiquer une heure valide", "travel.creation.travel.title": "Trajet", "travel.creation.travel.titleHelper": "Veuillez indiquer un nom valide", + "travel.creation.loginNotice": "Entrez votre e-mail pour ajouter un trajet. Vous recevrez un lien de connexion à l’adresse renseignée.", "travel.errors.cant_create": "Impossible de créer le trajet", "travel.errors.cant_remove": "Impossible de supprimer le trajet", "travel.errors.cant_remove_passenger": "Impossible de supprimer le passager",

@@ -237,6 +239,7 @@ "travel.fields.details": "Notes",

"travel.fields.phone": "Contact", "travel.passengers.add": "Ajouter un passager", "travel.passengers.add_me": "S'ajouter", + "travel.passengers.add_me.loginNotice": "Entrez votre e-mail pour vous ajouter au trajet. Vous recevrez un lien de connexion à l’adresse renseignée.", "travel.passengers.add_someone": "Ajouter quelqu'un", "travel.passengers.add_to_car": "Ajouter au trajet", "travel.passengers.add_to_travel": "Ajouter au trajet",
M frontend/locales/it.jsonfrontend/locales/it.json

@@ -228,7 +228,6 @@ "profile.password_changed": "Password aggiornata",

"profile.stripe_link.button": "Cronologia", "profile.title": "Profilo", "signin.login": "$t(menu.login)", - "signin.no_account": "Non sei registrato/a?", "signin.or": "OPPURE", "signin.withGoogle": "Usa un account Google", "signup.email": "Email",
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -184,7 +184,6 @@ "profile.title": "Profiel",

"signin.email": "E-mailadres", "signin.emailConfirmation": "Uw account is bevestigd - u kunt nu inloggen.", "signin.login": "$t(menu.login)", - "signin.no_account": "Heeft u nog geen account?", "signin.or": "OF", "signin.title": "Inloggen", "signin.withGoogle": "Inloggen met Google-account",
M frontend/pages/auth/login.tsxfrontend/pages/auth/login.tsx

@@ -1,103 +1,40 @@

import {useTranslation} from 'react-i18next'; import Layout from '../../layouts/Centered'; import { - Button, Card, CardContent, CardMedia, Container, - Stack, - TextField, Typography, - FormHelperText, } from '@mui/material'; import Logo from '../../components/Logo'; import {getSession} from 'next-auth/react'; import pageUtils from '../../lib/pageUtils'; import Cookies from 'cookies'; -import {useState} from 'react'; -import LoginGoogle from '../../containers/LoginGoogle'; -import {useSendMagicLinkMutation} from '../../generated/graphql'; +import LoginForm from '../../containers/LoginForm'; interface Props { error?: string; } const Login = (props: Props) => { - const {error} = props; - const {t, i18n} = useTranslation(); - const [email, setEmail] = useState(''); - const [sent, setSent] = useState(false); - const [magicLinkError, setMagicLinkError] = useState<string>(null); - const [sendMagicLink] = useSendMagicLinkMutation(); - - const handleSubmit = async (e: React.FormEvent<HTMLButtonElement>) => { - try { - setMagicLinkError(null); - if (email) await sendMagicLink({variables: {email, lang: i18n.language}}); - setSent(true); - } catch (error) { - console.error(error); - if (error.message === 'GoogleAccount') setMagicLinkError(error.message); - } - }; + const {t} = useTranslation(); return ( <Layout menuTitle={t('signin.title')} displayMenu={false}> <Container maxWidth="xs"> <Card sx={{pt: 2, width: '100%'}}> <CardMedia component={Logo} /> - <CardContent> - <Stack spacing={2}> - <Typography variant="h6" align="center"> - {t('signin.title')} - </Typography> - {(error || magicLinkError) && ( - <FormHelperText error sx={{textAlign: 'center'}}> - {t(errorsMap[error || magicLinkError])} - </FormHelperText> - )} - {!sent && ( - <> - <TextField - label={t`signin.email`} - fullWidth - required - value={email} - onChange={e => setEmail(e.target.value)} - type="email" - /> - <Button - fullWidth - color="primary" - variant="contained" - disabled={!email} - onClick={handleSubmit} - > - {t('signin.sendLink')} - </Button> - <Typography align="center">{t('signin.or')}</Typography> - <LoginGoogle /> - </> - )} - {sent && ( - <Typography - variant="body2" - align="center" - >{t`signin.check_email`}</Typography> - )} - </Stack> + <Typography variant="h6" align="center"> + {t('signin.title')} + </Typography> + <LoginForm error={props.error} showGoogleAuth /> </CardContent> </Card> </Container> </Layout> ); -}; - -const errorsMap = { - CredentialsSignin: 'signin.errors.CredentialsSignin', - GoogleAccount: 'signin.errors.GoogleAccount', }; export const getServerSideProps = async (context: any) => {
M frontend/pages/e/[uuid]/index.tsxfrontend/pages/e/[uuid]/index.tsx

@@ -1,9 +1,7 @@

-import {useState, PropsWithChildren} from 'react'; -import Box from '@mui/material/Box'; +import {PropsWithChildren} from 'react'; import TravelColumns from '../../../containers/TravelColumns'; -import NewTravelDialog from '../../../containers/NewTravelDialog'; import pageUtils from '../../../lib/pageUtils'; -import EventLayout, {TabComponent} from '../../../layouts/Event'; +import EventLayout from '../../../layouts/Event'; import {EventByUuidDocument} from '../../../generated/graphql'; import {getLocaleForLang} from '../../../lib/getLocale';

@@ -13,21 +11,7 @@ announcement?: string;

} const Page = (props: PropsWithChildren<Props>) => { - return <EventLayout {...props} Tab={TravelsTab} />; -}; - -const TravelsTab: TabComponent<Props> = () => { - const [openNewTravelDialog, setNewTravelDialog] = useState(false); - - return ( - <Box> - <TravelColumns showTravelModal={() => setNewTravelDialog(true)} /> - <NewTravelDialog - opened={openNewTravelDialog} - toggle={() => setNewTravelDialog(false)} - /> - </Box> - ); + return <EventLayout {...props} Tab={TravelColumns} />; }; export const getServerSideProps = pageUtils.getServerSideProps(