Client - add privacy review when user has not accepted the last policy

This commit is contained in:
Sam 2023-02-26 18:54:09 +01:00
parent 3834e71c95
commit 713b86bfb3
12 changed files with 207 additions and 3 deletions

View File

@ -0,0 +1,25 @@
<template>
<div class="privacy-policy-message">
<span>
<i18n-t keypath="user.LAST_PRIVACY_POLICY_TO_VALIDATE">
<router-link to="/profile/edit/privacy-policy">
{{ $t('user.REVIEW') }}
</router-link>
</i18n-t>
</span>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
@import '~@/scss/vars.scss';
.privacy-policy-message {
background: var(--alert-background-color);
color: var(--alert-color);
border-radius: $border-radius;
padding: $default-padding $default-padding*2;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<div id="user-privacy-policy">
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
<div v-if="user.accepted_privacy_policy">
<p>
<i18n-t keypath="user.YOU_HAVE_ACCEPTED_PRIVACY_POLICY">
<router-link to="/privacy-policy">
{{ $t('privacy_policy.TITLE') }}
</router-link>
</i18n-t>
</p>
<button class="cancel" @click="$router.push('/profile')">
{{ $t('user.PROFILE.BACK_TO_PROFILE') }}
</button>
</div>
<form v-else @submit.prevent="onSubmit()">
<div class="policy-content">
<PrivacyPolicy />
</div>
<label
for="accepted_policy"
class="accepted_policy"
>
<input
type="checkbox"
id="accepted_policy"
required
v-model="acceptedPolicy"
/>
<span>
<i18n-t keypath="user.READ_AND_ACCEPT_PRIVACY_POLICY">
{{ $t('privacy_policy.TITLE') }}
</i18n-t>
</span>
</label>
<router-link to="/profile/edit/account">
{{ $t('user.I_WANT_TO_DELETE_MY_ACCOUNT') }}
</router-link>
<div class="form-buttons">
<button class="confirm" type="submit">
{{ $t('buttons.SUBMIT') }}
</button>
<button class="cancel" @click="$router.push('/profile')">
{{ $t('user.PROFILE.BACK_TO_PROFILE') }}
</button>
</div>
</form>
</div>
</template>
<script setup lang="ts">
import { ComputedRef, computed, ref, onUnmounted, toRefs } from 'vue'
import PrivacyPolicy from '@/components/PrivacyPolicy.vue'
import {AUTH_USER_STORE, ROOT_STORE} from '@/store/constants'
import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
interface Props {
user: IAuthUserProfile
}
const props = defineProps<Props>()
const { user } = toRefs(props)
const store = useStore()
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
)
const acceptedPolicy= ref(false)
function onSubmit() {
store.dispatch(
AUTH_USER_STORE.ACTIONS.ACCEPT_PRIVACY_POLICY, acceptedPolicy.value
)
}
onUnmounted(() => {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
})
</script>
<style lang="scss" scoped>
@import '~@/scss/vars.scss';
#user-privacy-policy {
padding: $default-padding 0;
form {
display: flex;
flex-direction: column;
gap: $default-padding;
.policy-content {
height: 500px;
border:1px solid #ccc;
overflow: auto;
margin: $default-margin;
border-radius: $border-radius;
@media screen and (max-width: $small-limit) {
margin: $default-margin 0;
font-size: .9em;
}
.privacy-policy-text {
width: auto;
}
}
.form-buttons {
display: flex;
gap: $default-padding;
flex-direction: row;
@media screen and (max-width: $x-small-limit) {
flex-direction: column;
}
}
}
}
</style>

View File

@ -34,7 +34,7 @@
const store = useStore() const store = useStore()
const { user, tab } = toRefs(props) const { user, tab } = toRefs(props)
const tabs = ['PROFILE', 'ACCOUNT', 'PICTURE', 'PREFERENCES', 'SPORTS'] const tabs = ['PROFILE', 'ACCOUNT', 'PICTURE', 'PREFERENCES', 'SPORTS', 'PRIVACY-POLICY']
const loading = computed( const loading = computed(
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING] () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
) )

View File

@ -37,6 +37,7 @@
switch (tab) { switch (tab) {
case 'ACCOUNT': case 'ACCOUNT':
case 'PICTURE': case 'PICTURE':
case 'PRIVACY-POLICY':
return `/profile/edit/${tab.toLocaleLowerCase()}` return `/profile/edit/${tab.toLocaleLowerCase()}`
case 'APPS': case 'APPS':
case 'PREFERENCES': case 'PREFERENCES':

View File

@ -10,8 +10,10 @@
"ENTER_PASSWORD": "Enter a password", "ENTER_PASSWORD": "Enter a password",
"FILTER_ON_USERNAME": "Filter on username", "FILTER_ON_USERNAME": "Filter on username",
"HIDE_PASSWORD": "hide password", "HIDE_PASSWORD": "hide password",
"I_WANT_TO_DELETE_MY_ACCOUNT": "I want to delete my account",
"INVALID_TOKEN": "Invalid token, please request a new password reset.", "INVALID_TOKEN": "Invalid token, please request a new password reset.",
"LANGUAGE": "Language", "LANGUAGE": "Language",
"LAST_PRIVACY_POLICY_TO_VALIDATE": "The privacy policy has been updated, please {0} it before proceeding.",
"LOGIN": "Login", "LOGIN": "Login",
"LOGOUT": "Logout", "LOGOUT": "Logout",
"LOG_IN": "log in", "LOG_IN": "log in",
@ -68,6 +70,7 @@
"PICTURE_REMOVE": "Remove picture", "PICTURE_REMOVE": "Remove picture",
"PICTURE_UPDATE": "Update picture", "PICTURE_UPDATE": "Update picture",
"PREFERENCES_EDITION": "Preferences edition", "PREFERENCES_EDITION": "Preferences edition",
"PRIVACY-POLICY_EDITION": "Privacy policy",
"PROFILE_EDITION": "Profile edition", "PROFILE_EDITION": "Profile edition",
"REGISTRATION_DATE": "Registration date", "REGISTRATION_DATE": "Registration date",
"SPORT": { "SPORT": {
@ -89,6 +92,7 @@
"APPS": "apps", "APPS": "apps",
"PICTURE": "picture", "PICTURE": "picture",
"PREFERENCES": "preferences", "PREFERENCES": "preferences",
"PRIVACY-POLICY": "privacy policy",
"PROFILE": "profile", "PROFILE": "profile",
"SPORTS": "sports" "SPORTS": "sports"
}, },
@ -104,9 +108,11 @@
"REGISTER_DISABLED": "Sorry, registration is disabled.", "REGISTER_DISABLED": "Sorry, registration is disabled.",
"RESENT_ACCOUNT_CONFIRMATION": "Resend account confirmation email", "RESENT_ACCOUNT_CONFIRMATION": "Resend account confirmation email",
"RESET_PASSWORD": "Reset your password", "RESET_PASSWORD": "Reset your password",
"REVIEW": "review",
"SHOW_PASSWORD": "show password", "SHOW_PASSWORD": "show password",
"THIS_USER_ACCOUNT_IS_INACTIVE": "This user account is inactive.", "THIS_USER_ACCOUNT_IS_INACTIVE": "This user account is inactive.",
"USERNAME": "Username", "USERNAME": "Username",
"USERNAME_INFO": "3 to 30 characters required, only alphanumeric characters and the underscore character \"_\" allowed.", "USERNAME_INFO": "3 to 30 characters required, only alphanumeric characters and the underscore character \"_\" allowed.",
"USER_PICTURE": "user picture" "USER_PICTURE": "user picture",
"YOU_HAVE_ACCEPTED_PRIVACY_POLICY": "You have accepted the {0}."
} }

View File

@ -10,8 +10,10 @@
"ENTER_PASSWORD": "Saisissez un mot de passe", "ENTER_PASSWORD": "Saisissez un mot de passe",
"FILTER_ON_USERNAME": "Filtrer sur le nom d'utilisateur", "FILTER_ON_USERNAME": "Filtrer sur le nom d'utilisateur",
"HIDE_PASSWORD": "masquer le mot de passe", "HIDE_PASSWORD": "masquer le mot de passe",
"I_WANT_TO_DELETE_MY_ACCOUNT": "Je souhaite supprimer mon compte",
"INVALID_TOKEN": "Jeton invalide, veuillez demander une nouvelle réinitialisation de mot de passe.", "INVALID_TOKEN": "Jeton invalide, veuillez demander une nouvelle réinitialisation de mot de passe.",
"LANGUAGE": "Langue", "LANGUAGE": "Langue",
"LAST_PRIVACY_POLICY_TO_VALIDATE": "La politique de confidentialité a été mise à jour. Veuillez l'{0} avant de poursuivre.",
"LOGIN": "Se connecter", "LOGIN": "Se connecter",
"LOGOUT": "Se déconnecter", "LOGOUT": "Se déconnecter",
"LOG_IN": "connecter", "LOG_IN": "connecter",
@ -68,6 +70,7 @@
"PICTURE_REMOVE": "Supprimer", "PICTURE_REMOVE": "Supprimer",
"PICTURE_UPDATE": "Mettre à jour l'image", "PICTURE_UPDATE": "Mettre à jour l'image",
"PREFERENCES_EDITION": "Mise à jour des préférences", "PREFERENCES_EDITION": "Mise à jour des préférences",
"PRIVACY-POLICY_EDITION": "Politique de confidentialité",
"PROFILE_EDITION": "Mise à jour du profil", "PROFILE_EDITION": "Mise à jour du profil",
"REGISTRATION_DATE": "Date d'inscription", "REGISTRATION_DATE": "Date d'inscription",
"SPORT": { "SPORT": {
@ -89,6 +92,7 @@
"APPS": "apps", "APPS": "apps",
"PICTURE": "image", "PICTURE": "image",
"PREFERENCES": "préférences", "PREFERENCES": "préférences",
"PRIVACY-POLICY": "politique de confidentialité",
"PROFILE": "profil", "PROFILE": "profil",
"SPORTS": "sports" "SPORTS": "sports"
}, },
@ -104,9 +108,11 @@
"REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.", "REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.",
"RESENT_ACCOUNT_CONFIRMATION": "Envoyer à nouveau le courriel de confirmation de compte", "RESENT_ACCOUNT_CONFIRMATION": "Envoyer à nouveau le courriel de confirmation de compte",
"RESET_PASSWORD": "Réinitialiser votre mot de passe", "RESET_PASSWORD": "Réinitialiser votre mot de passe",
"REVIEW": "accepter",
"SHOW_PASSWORD": "afficher le mot de passe", "SHOW_PASSWORD": "afficher le mot de passe",
"THIS_USER_ACCOUNT_IS_INACTIVE": "Le compte de cet utilisateur est inactif.", "THIS_USER_ACCOUNT_IS_INACTIVE": "Le compte de cet utilisateur est inactif.",
"USERNAME": "Nom d'utilisateur", "USERNAME": "Nom d'utilisateur",
"USERNAME_INFO": "3 à 30 caractères requis, seuls les caractères alphanumériques et le caractère _ sont autorisés.", "USERNAME_INFO": "3 à 30 caractères requis, seuls les caractères alphanumériques et le caractère _ sont autorisés.",
"USER_PICTURE": "photo de l'utilisateur" "USER_PICTURE": "photo de l'utilisateur",
"YOU_HAVE_ACCEPTED_PRIVACY_POLICY": "Vous avez accepté la {0}."
} }

View File

@ -12,6 +12,7 @@ import UserAccountEdition from '@/components/User/ProfileEdition/UserAccountEdit
import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue' import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue'
import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.vue' import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.vue'
import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPreferencesEdition.vue' import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPreferencesEdition.vue'
import UserPrivacyPolicyValidation from '@/components/User/ProfileEdition/UserPrivacyPolicyValidation.vue'
import AddUserApp from '@/components/User/UserApps/AddUserApp.vue' import AddUserApp from '@/components/User/UserApps/AddUserApp.vue'
import AuthorizeUserApp from '@/components/User/UserApps/AuthorizeUserApp.vue' import AuthorizeUserApp from '@/components/User/UserApps/AuthorizeUserApp.vue'
import UserApps from '@/components/User/UserApps/index.vue' import UserApps from '@/components/User/UserApps/index.vue'
@ -219,6 +220,11 @@ const routes: Array<RouteRecordRaw> = [
component: UserSportPreferences, component: UserSportPreferences,
props: { isEdition: true }, props: { isEdition: true },
}, },
{
path: 'privacy-policy',
name: 'UserPrivacyPolicy',
component: UserPrivacyPolicyValidation,
},
], ],
}, },
], ],

View File

@ -427,4 +427,24 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
}) })
.catch((error) => handleError(context, error)) .catch((error) => handleError(context, error))
}, },
[AUTH_USER_STORE.ACTIONS.ACCEPT_PRIVACY_POLICY](
context: ActionContext<IAuthUserState, IRootState>,
acceptedPolicy: boolean
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
authApi
.post('auth/account/privacy-policy', {
accepted_policy: acceptedPolicy,
})
.then((res) => {
if (res.data.status === 'success') {
context
.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
.then(() => router.push('/profile'))
} else {
handleError(context, null)
}
})
.catch((error) => handleError(context, error))
},
} }

View File

@ -1,4 +1,5 @@
export enum AuthUserActions { export enum AuthUserActions {
ACCEPT_PRIVACY_POLICY = 'ACCEPT_PRIVACY_POLICY',
CHECK_AUTH_USER = 'CHECK_AUTH_USER', CHECK_AUTH_USER = 'CHECK_AUTH_USER',
CONFIRM_ACCOUNT = 'CONFIRM_ACCOUNT', CONFIRM_ACCOUNT = 'CONFIRM_ACCOUNT',
CONFIRM_EMAIL = 'CONFIRM_EMAIL', CONFIRM_EMAIL = 'CONFIRM_EMAIL',

View File

@ -110,6 +110,11 @@ export interface IAuthUserActions {
[AUTH_USER_STORE.ACTIONS.DELETE_PICTURE]( [AUTH_USER_STORE.ACTIONS.DELETE_PICTURE](
context: ActionContext<IAuthUserState, IRootState> context: ActionContext<IAuthUserState, IRootState>
): void ): void
[AUTH_USER_STORE.ACTIONS.ACCEPT_PRIVACY_POLICY](
context: ActionContext<IAuthUserState, IRootState>,
acceptedPolicy: boolean
): void
} }
export interface IAuthUserGetters { export interface IAuthUserGetters {

View File

@ -25,6 +25,7 @@ export interface IUserProfile {
} }
export interface IAuthUserProfile extends IUserProfile { export interface IAuthUserProfile extends IUserProfile {
accepted_privacy_policy: boolean
display_ascent: boolean display_ascent: boolean
imperial_units: boolean imperial_units: boolean
language: string | null language: string | null

View File

@ -36,6 +36,12 @@
</div> </div>
</div> </div>
</div> </div>
<div
class="container privacy-policy-message"
v-if="!authUser.accepted_privacy_policy"
>
<PrivacyPolicyToAccept />
</div>
<div class="container"> <div class="container">
<UserStatsCards :user="authUser" /> <UserStatsCards :user="authUser" />
</div> </div>
@ -80,6 +86,7 @@
import UserMonthStats from '@/components/Dashboard/UserMonthStats.vue' import UserMonthStats from '@/components/Dashboard/UserMonthStats.vue'
import UserRecords from '@/components/Dashboard/UserRecords/index.vue' import UserRecords from '@/components/Dashboard/UserRecords/index.vue'
import UserStatsCards from '@/components/Dashboard/UserStatsCards/index.vue' import UserStatsCards from '@/components/Dashboard/UserStatsCards/index.vue'
import PrivacyPolicyToAccept from '@/components/PrivacyPolicyToAccept.vue'
import { AUTH_USER_STORE, SPORTS_STORE } from '@/store/constants' import { AUTH_USER_STORE, SPORTS_STORE } from '@/store/constants'
import { ISport } from '@/types/sports' import { ISport } from '@/types/sports'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
@ -126,6 +133,11 @@
display: none; display: none;
} }
.privacy-policy-message {
display: flex;
justify-content: center;
}
@media screen and (max-width: $medium-limit) { @media screen and (max-width: $medium-limit) {
padding-bottom: 60px; padding-bottom: 60px;
.dashboard-container { .dashboard-container {