all repos — caroster @ a7f00a96575b17ed51637a09a10f0361ce410007

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

feat: :sparkles: Manually accept TOS an data privacy policy
Tim Izzo tim@octree.ch
Fri, 16 Feb 2024 11:48:46 +0000
commit

a7f00a96575b17ed51637a09a10f0361ce410007

parent

339cbcd2ddf4c97ce812f3c42a312a346e709a2e

M backend/src/api/setting/content-types/setting/schema.jsonbackend/src/api/setting/content-types/setting/schema.json

@@ -80,6 +80,22 @@ "localized": false

} }, "type": "string" + }, + "tos_link": { + "pluginOptions": { + "i18n": { + "localized": false + } + }, + "type": "string" + }, + "data_policy_link": { + "pluginOptions": { + "i18n": { + "localized": false + } + }, + "type": "string" } } }
M backend/src/extensions/users-permissions/content-types/user/schema.jsonbackend/src/extensions/users-permissions/content-types/user/schema.json

@@ -9,8 +9,7 @@ "pluralName": "users",

"displayName": "User" }, "options": { - "draftAndPublish": false, - "timestamps": true + "draftAndPublish": false }, "attributes": { "username": {

@@ -100,7 +99,10 @@ "default": false

}, "lang": { "type": "enumeration", - "enum": ["fr", "en"], + "enum": [ + "fr", + "en" + ], "default": "fr" }, "newsletterConsent": {

@@ -116,6 +118,9 @@ "type": "relation",

"relation": "oneToMany", "target": "api::notification.notification", "mappedBy": "user" + }, + "tosAcceptationDate": { + "type": "datetime" } } }
M backend/src/graphql/auth/index.tsbackend/src/graphql/auth/index.ts

@@ -7,6 +7,7 @@ t.string("firstName");

t.string("lastName"); t.string("lang"); t.boolean("newsletterConsent"); + t.field("tosAcceptationDate", { type: "DateTime" }); }, }), ],
M backend/types/generated/contentTypes.d.tsbackend/types/generated/contentTypes.d.ts

@@ -722,7 +722,6 @@ displayName: 'User';

}; options: { draftAndPublish: false; - timestamps: true; }; attributes: { username: Attribute.String &

@@ -779,6 +778,7 @@ 'plugin::users-permissions.user',

'oneToMany', 'api::notification.notification' >; + tosAcceptationDate: Attribute.DateTime; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation<

@@ -1132,6 +1132,18 @@ localized: false;

}; }>; stripe_dashboard_link: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: false; + }; + }>; + tos_link: Attribute.String & + Attribute.SetPluginOptions<{ + i18n: { + localized: false; + }; + }>; + data_policy_link: Attribute.String & Attribute.SetPluginOptions<{ i18n: { localized: false;
M frontend/containers/MailSignUpForm/index.tsxfrontend/containers/MailSignUpForm/index.tsx

@@ -9,11 +9,13 @@ import CardContent from '@mui/material/CardContent';

import Typography from '@mui/material/Typography'; import CircularProgress from '@mui/material/CircularProgress'; import {useTheme} from '@mui/material/styles'; -import {useTranslation} from 'react-i18next'; +import {Trans, useTranslation} from 'react-i18next'; import {useRouter} from 'next/router'; import useToastsStore from '../../stores/useToastStore'; import SignUpActions from './SignupActions'; import {useRegisterMutation} from '../../generated/graphql'; +import useSettings from '../../hooks/useSettings'; +import moment from 'moment'; const SignUp = () => { const {t, i18n} = useTranslation();

@@ -27,19 +29,22 @@ const [lastName, setLastName] = useState('');

const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [newsletterConsent, setNewsletterConsent] = useState(false); + const [tosConsent, setTosConsent] = useState(false); const [register] = useRegisterMutation(); + const settings = useSettings(); const canSubmit = useMemo( () => [firstName, lastName, email, password].filter(s => s.length < 2) - .length === 0, - [firstName, lastName, email, password] + .length === 0 && tosConsent, + [firstName, lastName, email, password, tosConsent] ); const onSubmit = async e => { e.preventDefault?.(); if (isLoading) return; setIsLoading(true); + const tosAcceptationDate = tosConsent ? moment().toISOString() : null; try { const lang = i18n.language; await register({

@@ -51,6 +56,7 @@ password,

firstName, lastName, newsletterConsent, + tosAcceptationDate, lang, }, },

@@ -140,11 +146,15 @@ type="password"

/> </Box> <FormControlLabel - sx={{width: '100%', margin: theme.spacing(2, 0), padding: theme.spacing(0, 4)}} + sx={{ + width: '100%', + mt: 2, + px: 5.5, + }} componentsProps={{typography: {align: 'left', variant: 'body2'}}} control={ <Checkbox - sx={{padding: 0, marginRight: theme.spacing(2)}} + sx={{p: 0, mr: 2}} color="primary" value={newsletterConsent} onChange={({target: {checked = false}}) =>

@@ -154,8 +164,30 @@ />

} label={t('signup.newsletter.consent')} /> + <FormControlLabel + sx={{width: '100%', mt: 2, px: 5.5}} + componentsProps={{typography: {align: 'left', variant: 'body2'}}} + label={ + <Trans + i18nKey="signup.tos.consent" + components={{ + 'tos-link': <a href={settings.tos_link} target="_blank" />, + 'data-privacy-link': ( + <a href={settings.data_policy_link} target="_blank" /> + ), + }} + /> + } + control={ + <Checkbox + sx={{p: 0, mr: 2}} + value={tosConsent} + onChange={e => setTosConsent(e.target.checked)} + /> + } + /> - <Box sx={contentSx}> + <Box sx={contentSx} mt={2}> <Button color="primary" variant="contained"
M frontend/generated/graphql.tsxfrontend/generated/graphql.tsx

@@ -1344,6 +1344,7 @@ about_link?: Maybe<Scalars['String']['output']>;

announcement?: Maybe<Scalars['String']['output']>; code_link?: Maybe<Scalars['String']['output']>; createdAt?: Maybe<Scalars['DateTime']['output']>; + data_policy_link?: Maybe<Scalars['String']['output']>; faq_link?: Maybe<Scalars['String']['output']>; gtm_id?: Maybe<Scalars['String']['output']>; locale?: Maybe<Scalars['String']['output']>;

@@ -1351,6 +1352,7 @@ localizations?: Maybe<SettingRelationResponseCollection>;

matomo_script_url?: Maybe<Scalars['String']['output']>; opencollective_link?: Maybe<Scalars['String']['output']>; stripe_dashboard_link?: Maybe<Scalars['String']['output']>; + tos_link?: Maybe<Scalars['String']['output']>; updatedAt?: Maybe<Scalars['DateTime']['output']>; };

@@ -1369,11 +1371,13 @@ export type SettingInput = {

about_link?: InputMaybe<Scalars['String']['input']>; announcement?: InputMaybe<Scalars['String']['input']>; code_link?: InputMaybe<Scalars['String']['input']>; + data_policy_link?: InputMaybe<Scalars['String']['input']>; faq_link?: InputMaybe<Scalars['String']['input']>; gtm_id?: InputMaybe<Scalars['String']['input']>; matomo_script_url?: InputMaybe<Scalars['String']['input']>; opencollective_link?: InputMaybe<Scalars['String']['input']>; stripe_dashboard_link?: InputMaybe<Scalars['String']['input']>; + tos_link?: InputMaybe<Scalars['String']['input']>; }; export type SettingRelationResponseCollection = {

@@ -1784,6 +1788,7 @@ lang?: InputMaybe<Scalars['String']['input']>;

lastName?: InputMaybe<Scalars['String']['input']>; newsletterConsent?: InputMaybe<Scalars['Boolean']['input']>; password: Scalars['String']['input']; + tosAcceptationDate?: InputMaybe<Scalars['DateTime']['input']>; username: Scalars['String']['input']; };

@@ -1872,6 +1877,7 @@ onboardingCreator?: Maybe<Scalars['Boolean']['output']>;

onboardingUser?: Maybe<Scalars['Boolean']['output']>; provider?: Maybe<Scalars['String']['output']>; role?: Maybe<UsersPermissionsRoleEntityResponse>; + tosAcceptationDate?: Maybe<Scalars['DateTime']['output']>; updatedAt?: Maybe<Scalars['DateTime']['output']>; username: Scalars['String']['output']; vehicles?: Maybe<VehicleRelationResponseCollection>;

@@ -1939,6 +1945,7 @@ password?: InputMaybe<StringFilterInput>;

provider?: InputMaybe<StringFilterInput>; resetPasswordToken?: InputMaybe<StringFilterInput>; role?: InputMaybe<UsersPermissionsRoleFiltersInput>; + tosAcceptationDate?: InputMaybe<DateTimeFilterInput>; updatedAt?: InputMaybe<DateTimeFilterInput>; username?: InputMaybe<StringFilterInput>; vehicles?: InputMaybe<VehicleFiltersInput>;

@@ -1964,6 +1971,7 @@ password?: InputMaybe<Scalars['String']['input']>;

provider?: InputMaybe<Scalars['String']['input']>; resetPasswordToken?: InputMaybe<Scalars['String']['input']>; role?: InputMaybe<Scalars['ID']['input']>; + tosAcceptationDate?: InputMaybe<Scalars['DateTime']['input']>; username?: InputMaybe<Scalars['String']['input']>; vehicles?: InputMaybe<Array<InputMaybe<Scalars['ID']['input']>>>; };

@@ -2159,7 +2167,7 @@ locale: Scalars['I18NLocaleCode']['input'];

}>; -export type SettingQuery = { __typename?: 'Query', setting?: { __typename?: 'SettingEntityResponse', data?: { __typename?: 'SettingEntity', id?: string | null, attributes?: { __typename?: 'Setting', gtm_id?: string | null, about_link?: string | null, faq_link?: string | null, announcement?: string | null, matomo_script_url?: string | null, opencollective_link?: string | null, code_link?: string | null, stripe_dashboard_link?: string | null } | null } | null } | null }; +export type SettingQuery = { __typename?: 'Query', setting?: { __typename?: 'SettingEntityResponse', data?: { __typename?: 'SettingEntity', id?: string | null, attributes?: { __typename?: 'Setting', gtm_id?: string | null, about_link?: string | null, faq_link?: string | null, announcement?: string | null, matomo_script_url?: string | null, opencollective_link?: string | null, code_link?: string | null, stripe_dashboard_link?: string | null, tos_link?: string | null, data_policy_link?: string | null } | null } | null } | null }; export type TravelFieldsFragment = { __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, email: string } | null } | null } | null } | null }> } | null } | null };

@@ -3044,6 +3052,8 @@ matomo_script_url

opencollective_link code_link stripe_dashboard_link + tos_link + data_policy_link } } }
M frontend/graphql/setting.gqlfrontend/graphql/setting.gql

@@ -11,6 +11,8 @@ matomo_script_url

opencollective_link code_link stripe_dashboard_link + tos_link + data_policy_link } } }
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -192,7 +192,6 @@ "signin.register": "$t(menu.register)",

"signin.title": "Sign in", "signin.withGoogle": "Use a Google account", "signup.account_already": "Do you already have an account ?", - "signup.conditions": "By creating an account, you agree to [the terms of Caroster](https://caroster.io/en/terms)", "signup.create": "Create an account", "signup.createForm": "Create an account\ninformation to fullfill", "signup.email": "Email",

@@ -201,6 +200,7 @@ "signup.firstName": "First name",

"signup.lastName": "Last name", "signup.login": "$t(menu.login)", "signup.newsletter.consent": "I am interested in car pooling, I want to subscribe to the newsletter.", + "signup.tos.consent": "I accept <tos-link>terms of service</tos-link> and <data-privacy-link>data privacy policy</data-privacy-link>", "signup.password": "Password", "signup.submit": "Create your account", "signup.title": "Sign up",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -192,7 +192,6 @@ "signin.register": "$t(menu.register)",

"signin.title": "Se connecter", "signin.withGoogle": "Continuer avec Google", "signup.account_already": "Vous avez déjà un compte ?", - "signup.conditions": "En créant un compte, vous acceptez [les conditions d’utilisations](https://caroster.io/fr/conditions-utilisation) de Caroster", "signup.create": "Créer un compte", "signup.createForm": "Créer un compte\ninformations à remplir", "signup.email": "Email",

@@ -201,6 +200,7 @@ "signup.firstName": "Prénom",

"signup.lastName": "Nom", "signup.login": "$t(menu.login)", "signup.newsletter.consent": "Le covoiturage m'intéresse, je souhaite recevoir des nouvelles de Caroster", + "signup.tos.consent": "J'accepte les <tos-link>conditions d'utilisation</tos-link> et la <data-privacy-link>politique de gestion de données</data-privacy-link>", "signup.password": "Mot de passe", "signup.submit": "Créer un compte", "signup.title": "Inscription",
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -170,7 +170,6 @@ "signin.register": "$t(menu.register)",

"signin.title": "Inloggen", "signin.withGoogle": "Inloggen met Google-account", "signup.account_already": "Heeft u al een account?", - "signup.conditions": "Door een account aan te maken, gaat u akkoord met [de algemene voorwaarden van Caroster](https://caroster.io/en/terms).", "signup.create": "Account aanmaken", "signup.createForm": "Vul alle account-\ninformatie in", "signup.email": "E-mailadres",

@@ -179,6 +178,7 @@ "signup.firstName": "Voornaam",

"signup.lastName": "Achternaam", "signup.login": "$t(menu.login)", "signup.newsletter.consent": "Ik ben geïnteresseerd in carpooling en wil me abonneren op de nieuwsbrief.", + "signup.tos.consent": "", "signup.password": "Wachtwoord", "signup.submit": "Account aanmaken", "signup.title": "Registreren",
M frontend/locales/pl.jsonfrontend/locales/pl.json

@@ -174,7 +174,6 @@ "signin.register": "$t(menu.register)",

"signin.title": "Sign in", "signin.withGoogle": "Użyj konta Google", "signup.account_already": "Czy masz już konto?", - "signup.conditions": "Tworząc konto, zgadzasz się na [warunki korzystania z Caroster](https://caroster.io/en/terms)", "signup.create": "Stwórz konto", "signup.createForm": "Stwórz konto\ninformacje do wypełnienia", "signup.email": "E-mail",

@@ -183,6 +182,7 @@ "signup.firstName": "Imię",

"signup.lastName": "Nazwisko", "signup.login": "$t(menu.login)", "signup.newsletter.consent": "Interesuje mnie car pooling, chcę zapisać się do newslettera.", + "signup.tos.consent": "", "signup.password": "Hasło", "signup.submit": "Stwórz swoje konto", "signup.title": "Załóż konto",
M frontend/locales/sv.jsonfrontend/locales/sv.json

@@ -170,7 +170,6 @@ "signin.register": "",

"signin.title": "", "signin.withGoogle": "", "signup.account_already": "", - "signup.conditions": "", "signup.create": "", "signup.createForm": "", "signup.email": "",

@@ -179,6 +178,7 @@ "signup.firstName": "",

"signup.lastName": "", "signup.login": "", "signup.newsletter.consent": "", + "signup.tos.consent": "", "signup.password": "", "signup.submit": "", "signup.title": "",
M frontend/pages/auth/confirm/google.tsxfrontend/pages/auth/confirm/google.tsx

@@ -5,22 +5,29 @@ import FormControlLabel from '@mui/material/FormControlLabel';

import Checkbox from '@mui/material/Checkbox'; import Button from '@mui/material/Button'; import Box from '@mui/material/Box'; -import {useTranslation} from 'react-i18next'; +import {useTranslation, Trans} from 'react-i18next'; import {useState} from 'react'; import pageUtils from '../../../lib/pageUtils'; import CommonConfirm from '../../../layouts/ConfirmLayout'; import {useUpdateMeMutation} from '../../../generated/graphql'; import router from 'next/router'; +import useSettings from '../../../hooks/useSettings'; +import moment from 'moment'; const Confirm = () => { const theme = useTheme(); const {t} = useTranslation(); + const settings = useSettings(); + const [updateMe] = useUpdateMeMutation(); const [newsletterConsent, setNewsletterConsent] = useState(false); - const [updateMe] = useUpdateMeMutation(); + const [tosConsent, setTosConsent] = useState(false); const onSubmit = async () => { - await updateMe({variables: {userUpdate: {newsletterConsent}}}); + const tosAcceptationDate = tosConsent ? moment().toISOString() : null; + await updateMe({ + variables: {userUpdate: {newsletterConsent, tosAcceptationDate}}, + }); router.push('/dashboard'); };

@@ -36,10 +43,10 @@ <Typography align="center" sx={{margin: theme.spacing(5, 0)}}>

<Icon fontSize="large">mail</Icon> </Typography> <FormControlLabel - sx={{width: '100%', margin: theme.spacing(2, 0)}} + sx={{width: '100%', my: 2, mx: 0}} control={ <Checkbox - sx={{padding: 0, marginRight: theme.spacing(2)}} + sx={{p: 0, mr: 2}} color="primary" value={newsletterConsent} onChange={({target: {checked = false}}) =>

@@ -49,8 +56,34 @@ />

} label={t('signup.newsletter.consent')} /> - <Box sx={{textAlign: 'center'}}> - <Button variant="contained" color="secondary" onClick={onSubmit}> + <FormControlLabel + sx={{width: '100%', my: 2, mx: 0}} + label={ + <Trans + i18nKey="signup.tos.consent" + components={{ + 'tos-link': <a href={settings.tos_link} target="_blank" />, + 'data-privacy-link': ( + <a href={settings.tos_link} target="_blank" /> + ), + }} + /> + } + control={ + <Checkbox + sx={{p: 0, mr: 2}} + value={tosConsent} + onChange={e => setTosConsent(e.target.checked)} + /> + } + /> + <Box sx={{textAlign: 'center'}} mt={2}> + <Button + variant="contained" + color="secondary" + onClick={onSubmit} + disabled={!tosConsent} + > {t('generic.confirm')} </Button> </Box>
M frontend/pages/auth/register/index.tsxfrontend/pages/auth/register/index.tsx

@@ -14,7 +14,6 @@ import Logo from '../../../components/Logo';

import LanguagesIcon from '../../../containers/Languages/Icon'; import SignUpActions from '../../../containers/MailSignUpForm/SignupActions'; import LoginGoogle from '../../../containers/LoginGoogle'; -import Markdown from '../../../components/Markdown'; import pageUtils from '../../../lib/pageUtils'; const MailSignup = () => {

@@ -42,18 +41,18 @@ sx={{

display: 'flex', flexDirection: 'column', alignItems: 'center', + justifyContent: 'center', width: '100%', - padding: {sm: theme.spacing(0, 6), xs: 0}, + maxWidth: '15rem', + mt: 4, }} > - <Link href="/auth/register/mail" passHref> + <Link href="/auth/register/mail" passHref style={{width: '100%'}}> <Button color="primary" variant="contained" fullWidth - sx={{ - margin: theme.spacing(8, 1, 4, 1), - }} + sx={{mb: 2}} > {t('signup.with_mail')} </Button>

@@ -67,19 +66,6 @@ textAlign: 'center',

margin: theme.spacing(5, 0, 2, 0), }} > - <Markdown - sx={{ - marginBottom: theme.spacing(5), - '& a': { - color: theme.palette.primary.main, - }, - }} - variant="body1" - align="center" - > - {t('signup.conditions')} - </Markdown> - <Divider /> </Box> <Typography align="center" variant="body2">