all repos — caroster @ 59e1bf80898c7df9ebd6e6ef9222b0d5b25ef7a9

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

feat: ✨ Add event options page for Caroster plus module activation
#221
Simon Mulquin simon@octree.ch
Thu, 01 Feb 2024 13:11:58 +0000
commit

59e1bf80898c7df9ebd6e6ef9222b0d5b25ef7a9

parent

db3f904f944c2eaa754b0dc7f69973151a4ff8cb

M backend/config/permissions.tsbackend/config/permissions.ts

@@ -13,13 +13,13 @@ "api::passenger.passenger.delete",

"api::passenger.passenger.update", "api::page.page.find", "api::page.page.findOne", + "api::module.module.find", "api::setting.setting.find", "api::stripe.stripe.handleWebhook", ]; const authenticated = [ ...publicPerms, - "api::module.module.find", "plugin::users-permissions.user.me", ];
M backend/src/api/module/content-types/module/schema.jsonbackend/src/api/module/content-types/module/schema.json

@@ -43,15 +43,6 @@ },

"type": "string", "required": true }, - "caroster_plus_description": { - "pluginOptions": { - "i18n": { - "localized": true - } - }, - "type": "text", - "required": true - }, "caroster_plus_payment_link_id": { "pluginOptions": { "i18n": {

@@ -72,6 +63,14 @@ },

"type": "string", "required": true, "regex": "https://buy.stripe.com/.*" + }, + "caroster_plus_description": { + "pluginOptions": { + "i18n": { + "localized": true + } + }, + "type": "richtext" } } }
M backend/types/generated/contentTypes.d.tsbackend/types/generated/contentTypes.d.ts

@@ -945,13 +945,6 @@ i18n: {

localized: true; }; }>; - caroster_plus_description: Attribute.Text & - Attribute.Required & - Attribute.SetPluginOptions<{ - i18n: { - localized: true; - }; - }>; caroster_plus_payment_link_id: Attribute.String & Attribute.Required & Attribute.Private &

@@ -965,6 +958,12 @@ Attribute.Required &

Attribute.SetPluginOptions<{ i18n: { localized: false; + }; + }>; + caroster_plus_description: Attribute.RichText & + Attribute.SetPluginOptions<{ + i18n: { + localized: true; }; }>; createdAt: Attribute.DateTime;
A frontend/containers/CarosterPlusOption/index.tsx

@@ -0,0 +1,113 @@

+import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import Typography from '@mui/material/Typography'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import Tooltip from '@mui/material/Tooltip'; +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material//ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'; +import {useTranslation} from 'react-i18next'; +import CarosterPlusSettings from '../CarosterPlusSettings'; +import useLocale from '../../hooks/useLocale'; +import useProfile from '../../hooks/useProfile'; +import {Event as EventType, Module} from '../../generated/graphql'; +import Markdown from '../../components/Markdown'; + +interface Props { + event: EventType; + modulesSettings: Module; +} + +const CarosterPlusOption = ({event, modulesSettings}: Props) => { + const {t} = useTranslation(); + const {profile} = useProfile(); + const {locale} = useLocale(); + + const userIsCreator = profile?.email === event.email; + + const { + caroster_plus_name, + caroster_plus_description, + caroster_plus_price, + caroster_plus_payment_link, + } = modulesSettings; + + return ( + <Card + sx={{ + position: 'relative', + maxWidth: '100%', + width: '350px', + }} + > + <Box + display="flex" + justifyContent="space-between" + width="100%" + p={2} + py={1} + > + <Typography variant="h4" pt={1}> + {caroster_plus_name} + </Typography> + <Box pt={1}> + <Tooltip + title={t( + userIsCreator + ? 'options.plus.activationOK' + : 'options.plus.activationForbiden' + )} + > + <Box> + <Button + href={`${caroster_plus_payment_link}?client_reference_id=${event.uuid}&locale=${locale}`} + disabled={!userIsCreator} + sx={{ + backgroundColor: 'primary.light', + color: 'primary.main', + fontWeight: 'bold', + borderRadius: 8, + pl: 2, + pr: 1, + }} + > + {caroster_plus_price} {caroster_plus_price && 'EUR'}{' '} + <ChevronRightIcon /> + </Button> + </Box> + </Tooltip> + </Box> + </Box> + <Box p={2}> + <Markdown + variant="caption" + sx={{ + '& ul': { + pl: 2, + }, + }} + > + {caroster_plus_description} + </Markdown> + </Box> + <Divider /> + <ListItem + sx={{p: 2}} + secondaryAction={ + <Chip label={t('options.plus.creator')} size="small" /> + } + > + <ListItemIcon> + <AccountCircleOutlinedIcon /> + </ListItemIcon> + <ListItemText primary={event.email} /> + </ListItem> + </Card> + ); +}; + +export default CarosterPlusOption;
A frontend/containers/CarosterPlusSettings/index.tsx

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

+import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material//ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import AddIcon from '@mui/icons-material/Add'; +import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'; +import Typography from '@mui/material/Typography'; +import {useTranslation} from 'react-i18next'; +import {Event as EventType} from '../../generated/graphql'; + +interface Props { + event: EventType; +} + +const CarosterPlusSettings = ({event}: Props) => { + const {t} = useTranslation(); + + return ( + <Card + sx={{ + position: 'relative', + maxWidth: '100%', + width: '350px', + pb: 3, + }} + > + <Box + display="flex" + justifyContent="space-between" + width="100%" + p={2} + py={1} + > + <Typography variant="h4" pt={1}> + {t('options.plus.title')} + </Typography> + </Box> + <Divider /> + <Box p={2} display="flex" justifyContent="space-between" width="100%"> + <Typography pt={1} variant="body2" color="GrayText"> + {t('options.plus.admins')} + </Typography> + <Button variant="text" endIcon={<AddIcon />}> + {t('generic.add')} + </Button> + </Box> + <List disablePadding> + <ListItem + secondaryAction={ + <Chip label={t('options.plus.creator')} size="medium" /> + } + > + <ListItemIcon> + <AccountCircleOutlinedIcon /> + </ListItemIcon> + <ListItemText primary={event.email} /> + </ListItem> + </List> + </Card> + ); +}; + +export default CarosterPlusSettings;
M frontend/containers/DrawerMenu/DrawerMenuItem.tsxfrontend/containers/DrawerMenu/DrawerMenuItem.tsx

@@ -46,7 +46,7 @@ justifyContent: 'middle',

}} label={ <Icon - color="action" + color={active ? "primary" : "action"} className={active ? 'material-icons' : 'material-icons-outlined'} > {icon}
M frontend/containers/DrawerMenu/index.tsxfrontend/containers/DrawerMenu/index.tsx

@@ -100,6 +100,14 @@ }}

icon="info" active={router.pathname === `/e/[uuid]/details`} /> + <DrawerMenuItem + title={t('drawer.options')} + onClick={() => { + router.push(`/e/${uuid}/options`, null, {shallow: true}); + }} + icon="settings" + active={router.pathname === `/e/[uuid]/options`} + /> </Drawer> ); };
M frontend/generated/graphql.tsxfrontend/generated/graphql.tsx

@@ -283,6 +283,7 @@ createdAt?: Maybe<Scalars['DateTime']['output']>;

date?: Maybe<Scalars['Date']['output']>; description?: Maybe<Scalars['String']['output']>; email: Scalars['String']['output']; + enabled_modules?: Maybe<Scalars['JSON']['output']>; latitude?: Maybe<Scalars['Float']['output']>; longitude?: Maybe<Scalars['Float']['output']>; name: Scalars['String']['output'];

@@ -326,6 +327,7 @@ createdAt?: InputMaybe<DateTimeFilterInput>;

date?: InputMaybe<DateFilterInput>; description?: InputMaybe<StringFilterInput>; email?: InputMaybe<StringFilterInput>; + enabled_modules?: InputMaybe<JsonFilterInput>; id?: InputMaybe<IdFilterInput>; latitude?: InputMaybe<FloatFilterInput>; longitude?: InputMaybe<FloatFilterInput>;

@@ -346,6 +348,7 @@ address?: InputMaybe<Scalars['String']['input']>;

date?: InputMaybe<Scalars['Date']['input']>; description?: InputMaybe<Scalars['String']['input']>; email?: InputMaybe<Scalars['String']['input']>; + enabled_modules?: InputMaybe<Scalars['JSON']['input']>; latitude?: InputMaybe<Scalars['Float']['input']>; longitude?: InputMaybe<Scalars['Float']['input']>; name?: InputMaybe<Scalars['String']['input']>;

@@ -393,7 +396,7 @@ or?: InputMaybe<Array<InputMaybe<Scalars['Float']['input']>>>;

startsWith?: InputMaybe<Scalars['Float']['input']>; }; -export type GenericMorph = ContentReleasesRelease | ContentReleasesReleaseAction | EmailDesignerEmailTemplate | Event | I18NLocale | Notification | Page | Passenger | Setting | Travel | UploadFile | UploadFolder | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Vehicle; +export type GenericMorph = ContentReleasesRelease | ContentReleasesReleaseAction | EmailDesignerEmailTemplate | Event | I18NLocale | Module | Notification | Page | Passenger | Setting | Travel | UploadFile | UploadFolder | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | Vehicle; export type I18NLocale = { __typename?: 'I18NLocale';

@@ -506,6 +509,44 @@ or?: InputMaybe<Array<InputMaybe<Scalars['JSON']['input']>>>;

startsWith?: InputMaybe<Scalars['JSON']['input']>; }; +export type Module = { + __typename?: 'Module'; + caroster_plus_description?: Maybe<Scalars['String']['output']>; + caroster_plus_enabled?: Maybe<Scalars['Boolean']['output']>; + caroster_plus_name: Scalars['String']['output']; + caroster_plus_payment_link: Scalars['String']['output']; + caroster_plus_price?: Maybe<Scalars['Float']['output']>; + createdAt?: Maybe<Scalars['DateTime']['output']>; + locale?: Maybe<Scalars['String']['output']>; + localizations?: Maybe<ModuleRelationResponseCollection>; + updatedAt?: Maybe<Scalars['DateTime']['output']>; +}; + +export type ModuleEntity = { + __typename?: 'ModuleEntity'; + attributes?: Maybe<Module>; + id?: Maybe<Scalars['ID']['output']>; +}; + +export type ModuleEntityResponse = { + __typename?: 'ModuleEntityResponse'; + data?: Maybe<ModuleEntity>; +}; + +export type ModuleInput = { + caroster_plus_description?: InputMaybe<Scalars['String']['input']>; + caroster_plus_enabled?: InputMaybe<Scalars['Boolean']['input']>; + caroster_plus_name?: InputMaybe<Scalars['String']['input']>; + caroster_plus_payment_link?: InputMaybe<Scalars['String']['input']>; + caroster_plus_payment_link_id?: InputMaybe<Scalars['String']['input']>; + caroster_plus_price?: InputMaybe<Scalars['Float']['input']>; +}; + +export type ModuleRelationResponseCollection = { + __typename?: 'ModuleRelationResponseCollection'; + data: Array<ModuleEntity>; +}; + export type Mutation = { __typename?: 'Mutation'; /** Change user password. Confirm with the current password. */

@@ -514,6 +555,7 @@ createContentReleasesRelease?: Maybe<ContentReleasesReleaseEntityResponse>;

createContentReleasesReleaseAction?: Maybe<ContentReleasesReleaseActionEntityResponse>; createEmailDesignerEmailTemplate?: Maybe<EmailDesignerEmailTemplateEntityResponse>; createEvent?: Maybe<EventEntityResponse>; + createModuleLocalization?: Maybe<ModuleEntityResponse>; createNotification?: Maybe<NotificationEntityResponse>; createPage?: Maybe<PageEntityResponse>; /** Create a passenger */

@@ -531,6 +573,7 @@ deleteContentReleasesRelease?: Maybe<ContentReleasesReleaseEntityResponse>;

deleteContentReleasesReleaseAction?: Maybe<ContentReleasesReleaseActionEntityResponse>; deleteEmailDesignerEmailTemplate?: Maybe<EmailDesignerEmailTemplateEntityResponse>; deleteEvent?: Maybe<EventEntityResponse>; + deleteModule?: Maybe<ModuleEntityResponse>; deleteNotification?: Maybe<NotificationEntityResponse>; deletePage?: Maybe<PageEntityResponse>; deletePassenger?: Maybe<PassengerEntityResponse>;

@@ -563,6 +606,7 @@ /** Update an event using its UUID */

updateEventByUUID?: Maybe<EventEntityResponse>; updateFileInfo: UploadFileEntityResponse; updateMe: UsersPermissionsUserEntityResponse; + updateModule?: Maybe<ModuleEntityResponse>; updateNotification?: Maybe<NotificationEntityResponse>; updatePage?: Maybe<PageEntityResponse>; updatePassenger?: Maybe<PassengerEntityResponse>;

@@ -606,6 +650,13 @@ data: EventInput;

}; +export type MutationCreateModuleLocalizationArgs = { + data?: InputMaybe<ModuleInput>; + id?: InputMaybe<Scalars['ID']['input']>; + locale?: InputMaybe<Scalars['I18NLocaleCode']['input']>; +}; + + export type MutationCreateNotificationArgs = { data: NotificationInput; };

@@ -676,6 +727,11 @@

export type MutationDeleteEventArgs = { id: Scalars['ID']['input']; +}; + + +export type MutationDeleteModuleArgs = { + locale?: InputMaybe<Scalars['I18NLocaleCode']['input']>; };

@@ -815,6 +871,12 @@ data: UsersPermissionsUserInput;

}; +export type MutationUpdateModuleArgs = { + data: ModuleInput; + locale?: InputMaybe<Scalars['I18NLocaleCode']['input']>; +}; + + export type MutationUpdateNotificationArgs = { data: NotificationInput; id: Scalars['ID']['input'];

@@ -1060,6 +1122,7 @@ eventByUUID?: Maybe<EventEntityResponse>;

i18NLocale?: Maybe<I18NLocaleEntityResponse>; i18NLocales?: Maybe<I18NLocaleEntityResponseCollection>; me?: Maybe<UsersPermissionsMe>; + module?: Maybe<ModuleEntityResponse>; notification?: Maybe<NotificationEntityResponse>; notifications?: Maybe<NotificationEntityResponseCollection>; page?: Maybe<PageEntityResponse>;

@@ -1136,6 +1199,11 @@ sort?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;

}; +export type QueryModuleArgs = { + locale?: InputMaybe<Scalars['I18NLocaleCode']['input']>; +}; + + export type QueryNotificationArgs = { id?: InputMaybe<Scalars['ID']['input']>; };

@@ -1304,6 +1372,7 @@ passengers?: Maybe<PassengerRelationResponseCollection>;

phone_number?: Maybe<Scalars['String']['output']>; seats?: Maybe<Scalars['Int']['output']>; updatedAt?: Maybe<Scalars['DateTime']['output']>; + user?: Maybe<UsersPermissionsUserEntityResponse>; vehicleName?: Maybe<Scalars['String']['output']>; };

@@ -1341,6 +1410,7 @@ passengers?: InputMaybe<PassengerFiltersInput>;

phone_number?: InputMaybe<StringFilterInput>; seats?: InputMaybe<IntFilterInput>; updatedAt?: InputMaybe<DateTimeFilterInput>; + user?: InputMaybe<UsersPermissionsUserFiltersInput>; vehicleName?: InputMaybe<StringFilterInput>; };

@@ -1354,6 +1424,7 @@ meeting_longitude?: InputMaybe<Scalars['Float']['input']>;

passengers?: InputMaybe<Array<InputMaybe<Scalars['ID']['input']>>>; phone_number?: InputMaybe<Scalars['String']['input']>; seats?: InputMaybe<Scalars['Int']['input']>; + user?: InputMaybe<Scalars['ID']['input']>; vehicleName?: InputMaybe<Scalars['String']['input']>; };

@@ -1855,7 +1926,7 @@

export type ResetPasswordMutation = { __typename?: 'Mutation', resetPassword?: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null, user: { __typename?: 'UsersPermissionsMe', id: string, username: string, email?: string | null, confirmed?: boolean | null } } | null }; -export type EventFieldsFragment = { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null }; +export type EventFieldsFragment = { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, enabled_modules?: any | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null }; export type CreateEventMutationVariables = Exact<{ name: Scalars['String']['input'];

@@ -1869,7 +1940,7 @@ newsletter?: InputMaybe<Scalars['Boolean']['input']>;

}>; -export type CreateEventMutation = { __typename?: 'Mutation', createEvent?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; +export type CreateEventMutation = { __typename?: 'Mutation', createEvent?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, enabled_modules?: any | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; export type UpdateEventMutationVariables = Exact<{ uuid: Scalars['String']['input'];

@@ -1877,14 +1948,21 @@ eventUpdate: EventInput;

}>; -export type UpdateEventMutation = { __typename?: 'Mutation', updateEventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; +export type UpdateEventMutation = { __typename?: 'Mutation', updateEventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, enabled_modules?: any | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; export type EventByUuidQueryVariables = Exact<{ uuid: Scalars['String']['input']; }>; -export type EventByUuidQuery = { __typename?: 'Query', eventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; +export type EventByUuidQuery = { __typename?: 'Query', eventByUUID?: { __typename?: 'EventEntityResponse', data?: { __typename?: 'EventEntity', id?: string | null, attributes?: { __typename?: 'Event', uuid?: string | null, name: string, description?: string | null, enabled_modules?: any | null, email: string, date?: any | null, address?: string | null, latitude?: number | null, longitude?: number | null, position?: any | null, waitingPassengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, email?: string | null, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null, travels?: { __typename?: 'TravelRelationResponseCollection', data: Array<{ __typename?: 'TravelEntity', id?: string | null, attributes?: { __typename?: 'Travel', meeting?: string | null, meeting_latitude?: number | null, meeting_longitude?: number | null, departure?: any | null, details?: string | null, vehicleName?: string | null, phone_number?: string | null, seats?: number | null, passengers?: { __typename?: 'PassengerRelationResponseCollection', data: Array<{ __typename?: 'PassengerEntity', id?: string | null, attributes?: { __typename?: 'Passenger', name: string, location?: string | null, user?: { __typename?: 'UsersPermissionsUserEntityResponse', data?: { __typename?: 'UsersPermissionsUserEntity', id?: string | null, attributes?: { __typename?: 'UsersPermissionsUser', firstName?: string | null, lastName?: string | null } | null } | null } | null } | null }> } | null } | null }> } | null } | null } | null } | null }; + +export type ModuleQueryVariables = Exact<{ + locale: Scalars['I18NLocaleCode']['input']; +}>; + + +export type ModuleQuery = { __typename?: 'Query', module?: { __typename?: 'ModuleEntityResponse', data?: { __typename?: 'ModuleEntity', attributes?: { __typename?: 'Module', caroster_plus_name: string, caroster_plus_price?: number | null, caroster_plus_enabled?: boolean | null, caroster_plus_description?: string | null, caroster_plus_payment_link: string } | null } | null } | null }; export type UserNotificationsQueryVariables = Exact<{ maxItems?: InputMaybe<Scalars['Int']['input']>;

@@ -1999,6 +2077,7 @@ attributes {

uuid name description + enabled_modules email date address

@@ -2374,6 +2453,49 @@ }

export type EventByUuidQueryHookResult = ReturnType<typeof useEventByUuidQuery>; export type EventByUuidLazyQueryHookResult = ReturnType<typeof useEventByUuidLazyQuery>; export type EventByUuidQueryResult = Apollo.QueryResult<EventByUuidQuery, EventByUuidQueryVariables>; +export const ModuleDocument = gql` + query module($locale: I18NLocaleCode!) { + module(locale: $locale) { + data { + attributes { + caroster_plus_name + caroster_plus_price + caroster_plus_enabled + caroster_plus_description + caroster_plus_payment_link + } + } + } +} + `; + +/** + * __useModuleQuery__ + * + * To run a query within a React component, call `useModuleQuery` and pass it any options that fit your needs. + * When your component renders, `useModuleQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useModuleQuery({ + * variables: { + * locale: // value for 'locale' + * }, + * }); + */ +export function useModuleQuery(baseOptions: Apollo.QueryHookOptions<ModuleQuery, ModuleQueryVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery<ModuleQuery, ModuleQueryVariables>(ModuleDocument, options); + } +export function useModuleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ModuleQuery, ModuleQueryVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery<ModuleQuery, ModuleQueryVariables>(ModuleDocument, options); + } +export type ModuleQueryHookResult = ReturnType<typeof useModuleQuery>; +export type ModuleLazyQueryHookResult = ReturnType<typeof useModuleLazyQuery>; +export type ModuleQueryResult = Apollo.QueryResult<ModuleQuery, ModuleQueryVariables>; export const UserNotificationsDocument = gql` query UserNotifications($maxItems: Int = 20) { me {
M frontend/graphql/event.gqlfrontend/graphql/event.gql

@@ -4,6 +4,7 @@ attributes {

uuid name description + enabled_modules email date address
A frontend/graphql/module.gql

@@ -0,0 +1,13 @@

+query module($locale: I18NLocaleCode!) { + module(locale: $locale) { + data { + attributes{ + caroster_plus_name + caroster_plus_price + caroster_plus_enabled + caroster_plus_description + caroster_plus_payment_link + } + } + } +}
M frontend/graphql/user.gqlfrontend/graphql/user.gql

@@ -1,5 +1,4 @@

fragment UserFields on UsersPermissionsUser { - id username email confirmed
M frontend/layouts/Event.tsxfrontend/layouts/Event.tsx

@@ -9,15 +9,17 @@ import Layout from '../layouts/Default';

import EventBar from '../containers/EventBar'; import DrawerMenu from '../containers/DrawerMenu'; import AddToMyEventDialog from '../containers/AddToMyEventDialog'; -import {Event as EventType, useEventByUuidQuery} from '../generated/graphql'; +import {Event as EventType, useEventByUuidQuery, Module} from '../generated/graphql'; const POLL_INTERVAL = 10000; export type TabComponent = (props: { event: EventType & {id: string}; + modulesSettings: Module; }) => JSX.Element; interface Props { + modulesSettings?: Module; eventUUID: string; Tab: TabComponent; goBack?: () => void;

@@ -25,7 +27,7 @@ titleKey?: string;

} const EventLayout = (props: PropsWithChildren<Props>) => { - const {eventUUID, Tab, goBack, titleKey, ...pageProps} = props; + const {eventUUID, modulesSettings, Tab, goBack, titleKey, ...pageProps} = props; const {t} = useTranslation(); const theme = useTheme();

@@ -75,7 +77,7 @@ }}

id="event-content" > <EventBar title={t(titleKey)} goBack={goBack} event={event} onAdd={setIsAddToMyEvent} /> - <Tab event={event} /> + <Tab event={event} modulesSettings={modulesSettings}/> </Box> </Box> <AddToMyEventDialog
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -19,6 +19,7 @@ "dashboard.sections.past_plural": "Past carosters",

"dashboard.title": "$t(menu.dashboard)", "date.today": "Today", "drawer.information": "Information", + "drawer.options": "Options", "drawer.travels": "Travels", "drawer.waitingList": "Waiting list", "event.actions.add_to_my_events": "Add to my events",

@@ -62,7 +63,16 @@ "event.no_travel.desc": "1. Subscribe to the waiting list\n2. Share the event\n3. You will be notified when a new travel is added",

"event.no_travel.title": "There are currently no cars", "event.not_found": "Project not found", "event.title": "{{title}} - Caroster", + "options.plus.title": "Caroster plus", + "options.plus.admins": "Administrators", + "options.plus.creator": "Creator", + "options.plus.adminFeature": "Administrate your Caroster by adding administrators", + "options.plus.accessFeature": "Manage access and deletion rights", + "options.plus.nearbyTripFeature": "Receive notifications for nearby departures", + "options.plus.activationOK": "Add Caroster plus to your event", + "options.plus.activationForbiden": "Only the administrator is authorized to add modules", "generic.access": "Access", + "generic.add": "Add", "generic.cancel": "Cancel", "generic.confirm": "Confirm", "generic.create": "Create",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -19,6 +19,7 @@ "dashboard.sections.past_plural": "Carosters passés",

"dashboard.title": "$t(menu.dashboard)", "date.today": "Aujourd'hui", "drawer.information": "Information", + "drawer.options": "Options", "drawer.travels": "Trajets", "drawer.waitingList": "Liste d'attente", "event.actions.add_to_my_events": "Ajouter à mes évènements",

@@ -62,7 +63,16 @@ "event.no_travel.desc": "1. Inscrivez-vous dans la liste d’attente \n2. Partagez l’événement \n3. Vous serez notifié lorsqu’un nouveau trajet sera ajouté",

"event.no_travel.title": "Pas de voitures pour le moment", "event.not_found": "Projet introuvable", "event.title": "{{title}} - Caroster", + "options.plus.title": "Caroster plus", + "options.plus.admins": "Administrateurs", + "options.plus.creator": "Créateur", + "options.plus.adminFeature": "Contrôlez votre Caroster en ajoutant des administrateurs", + "options.plus.accessFeature": "Personnalisez les accès et les droits de suppression", + "options.plus.nearbyTripFeature": "Recevez des notifications pour les départs proches", + "options.plus.activationOK": "Ajoutez Caroster plus à votre évènement", + "options.plus.activationForbiden": "Seul l'administrateur est autorisé à ajouter des modules", "generic.access": "Accéder", + "generic.add": "Ajouter", "generic.cancel": "Annuler", "generic.confirm": "Confirmer", "generic.create": "Créer",
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -18,6 +18,7 @@ "dashboard.sections.past": "Afgelopen caroster",

"dashboard.sections.past_plural": "Afgelopen carosters", "dashboard.title": "$t(menu.dashboard)", "drawer.information": "Informatie", + "drawer.options": "Opties", "drawer.travels": "Reizen", "drawer.waitingList": "Wachtlijst", "event.actions.add_to_my_events": "Toevoegen aan mijn afspraken",

@@ -61,7 +62,16 @@ "event.no_travel.desc": "1. Zet uzelf op de wachtlijst;\n2. Deel de afspraak;\n3. Ontvang een melding zodra er een reis beschikbaar is.",

"event.no_travel.title": "Er zijn momenteel geen auto's", "event.not_found": "Dit project bestaat niet", "event.title": "{{title}} - Caroster", + "options.plus.title": "Caroster plus", + "options.plus.admins": "Beheerders", + "options.plus.creator": "Schepper", + "options.plus.adminFeature": "Beheer uw Caroster door beheerders toe te voegen", + "options.plus.accessFeature": "Toegangs- en verwijderingsrechten aanpassen", + "options.plus.nearbyTripFeature": "Ontvang meldingen voor vertrekken in de buurt", + "options.plus.activationOK": "Voeg Caroster plus toe aan uw evenement", + "options.plus.activationForbiden": "Alleen de beheerder is bevoegd om modules toe te voegen", "generic.access": "Toegang", + "generic.add": "toevoegen", "generic.cancel": "Annuleren", "generic.confirm": "Oké", "generic.create": "Aanmaken",
M frontend/locales/pl.jsonfrontend/locales/pl.json

@@ -21,6 +21,7 @@ "dashboard.sections.past_1": "",

"dashboard.sections.past_2": "", "dashboard.title": "", "drawer.information": "Informacje", + "drawer.options": "", "drawer.travels": "Podróże", "drawer.waitingList": "Lista oczekujących", "event.actions.add_to_my_events": "",

@@ -64,7 +65,16 @@ "event.no_travel.desc": "1. Zasubskrybuj listę oczekujących\n2. Udostępnij wydarzenie\n3. Dostaniesz powiadomienie kiedy zostanie dodana nowa podróż",

"event.no_travel.title": "Obecnie nie ma żadnych samochodów", "event.not_found": "Nie znaleziono projektu", "event.title": "{{title}} - Caroster", + "options.plus.title": "", + "options.plus.admins": "", + "options.plus.creator": "", + "options.plus.adminFeature": "", + "options.plus.accessFeature": "", + "options.plus.nearbyTripFeature": "", + "options.plus.activationOK": "", + "options.plus.activationForbiden": "", "generic.access": "Dostęp", + "generic.add": "toevoegen", "generic.cancel": "Anuluj", "generic.confirm": "Potwierdź", "generic.create": "Stwórz",
M frontend/locales/sv.jsonfrontend/locales/sv.json

@@ -18,6 +18,7 @@ "dashboard.sections.past": "",

"dashboard.sections.past_plural": "", "dashboard.title": "$t(menu.dashboard)", "drawer.information": "", + "drawer.options": "", "drawer.travels": "", "drawer.waitingList": "", "event.actions.add_to_my_events": "",

@@ -61,7 +62,16 @@ "event.no_travel.desc": "",

"event.no_travel.title": "", "event.not_found": "", "event.title": "", + "options.plus.title": "", + "options.plus.admins": "", + "options.plus.creator": "", + "options.plus.adminFeature": "", + "options.plus.accessFeature": "", + "options.plus.nearbyTripFeature": "", + "options.plus.activationOK": "", + "options.plus.activationForbiden": "", "generic.access": "", + "generic.add": "toevoegen", "generic.cancel": "", "generic.confirm": "", "generic.create": "",
A frontend/pages/e/[uuid]/options.tsx

@@ -0,0 +1,121 @@

+import Box from '@mui/material/Box'; +import Container from '@mui/material/Container'; +import {useTheme} from '@mui/material/styles'; +import {PropsWithChildren} from 'react'; +import pageUtils from '../../../lib/pageUtils'; +import useEventStore from '../../../stores/useEventStore'; +import EventLayout, {TabComponent} from '../../../layouts/Event'; +import { + EventByUuidDocument, + Module, + ModuleDocument, + Enum_Userspermissionsuser_Lang as SupportedLocales, +} from '../../../generated/graphql'; +import CarosterPlusOption from '../../../containers/CarosterPlusOption'; +import CarosterPlusSettings from '../../../containers/CarosterPlusSettings'; + +interface Props { + modulesSettings?: Module; + eventUUID: string; + announcement?: string; +} + +const Page = (props: PropsWithChildren<Props>) => { + return <EventLayout {...props} Tab={OptionsTab} />; +}; + +const OptionsTab: TabComponent = ({modulesSettings}) => { + const theme = useTheme(); + const event = useEventStore(s => s.event); + + if (!event) return null; + + const carosterPlusActivated = + event.enabled_modules?.includes('caroster-plus'); + + return ( + <Box position="relative"> + <Container + sx={{ + p: 4, + mt: 6, + mb: 11, + mx: 0, + [theme.breakpoints.down('md')]: { + p: 2, + }, + }} + > + {carosterPlusActivated && <CarosterPlusSettings event={event} />}{' '} + {modulesSettings && !carosterPlusActivated && ( + <CarosterPlusOption event={event} modulesSettings={modulesSettings} /> + )} + </Container> + </Box> + ); +}; + +export const getServerSideProps = pageUtils.getServerSideProps( + async (context, apolloClient) => { + const {uuid} = context.query; + const {host = ''} = context.req.headers; + let event = null; + let modulesSettings = null; + + // Fetch event + try { + const {data} = await apolloClient.query({ + query: EventByUuidDocument, + variables: {uuid}, + }); + event = data?.eventByUUID?.data; + } catch (error) { + return { + notFound: true, + }; + } + + // Fetch modules settings + try { + const {data} = await apolloClient.query({ + query: ModuleDocument, + variables: {locale: context.locale}, + }); + modulesSettings = data?.module?.data?.attributes || {}; + + const {caroster_plus_description, caroster_plus_name} = modulesSettings; + + if ( + caroster_plus_description && + caroster_plus_name && + String(caroster_plus_description).length === 0 && + String(caroster_plus_name).length === 0 + ) { + console.warn( + 'Module settings are not set for locale: ', + context.locale, + ' fallback to English' + ); + const {data: enData} = await apolloClient.query({ + query: ModuleDocument, + variables: {locale: SupportedLocales['en']}, + }); + modulesSettings = enData?.module?.data?.attributes; + } + } catch (error) { + console.error(error); + } + + return { + props: { + modulesSettings, + eventUUID: uuid, + metas: { + title: event?.attributes?.name || '', + url: `https://${host}${context.resolvedUrl}`, + }, + }, + }; + } +); +export default Page;