Client - add privacy review when user has not accepted the last policy
This commit is contained in:
		
							
								
								
									
										25
									
								
								fittrackee_client/src/components/PrivacyPolicyToAccept.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								fittrackee_client/src/components/PrivacyPolicyToAccept.vue
									
									
									
									
									
										Normal 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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
  const store = useStore()
 | 
			
		||||
 | 
			
		||||
  const { user, tab } = toRefs(props)
 | 
			
		||||
  const tabs = ['PROFILE', 'ACCOUNT', 'PICTURE', 'PREFERENCES', 'SPORTS']
 | 
			
		||||
  const tabs = ['PROFILE', 'ACCOUNT', 'PICTURE', 'PREFERENCES', 'SPORTS', 'PRIVACY-POLICY']
 | 
			
		||||
  const loading = computed(
 | 
			
		||||
    () => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
 | 
			
		||||
  )
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@
 | 
			
		||||
    switch (tab) {
 | 
			
		||||
      case 'ACCOUNT':
 | 
			
		||||
      case 'PICTURE':
 | 
			
		||||
      case 'PRIVACY-POLICY':
 | 
			
		||||
        return `/profile/edit/${tab.toLocaleLowerCase()}`
 | 
			
		||||
      case 'APPS':
 | 
			
		||||
      case 'PREFERENCES':
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,10 @@
 | 
			
		||||
    "ENTER_PASSWORD": "Enter a password",
 | 
			
		||||
    "FILTER_ON_USERNAME": "Filter on username",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "LANGUAGE": "Language",
 | 
			
		||||
    "LAST_PRIVACY_POLICY_TO_VALIDATE": "The privacy policy has been updated, please {0} it before proceeding.",
 | 
			
		||||
    "LOGIN": "Login",
 | 
			
		||||
    "LOGOUT": "Logout",
 | 
			
		||||
    "LOG_IN": "log in",
 | 
			
		||||
@@ -68,6 +70,7 @@
 | 
			
		||||
        "PICTURE_REMOVE": "Remove picture",
 | 
			
		||||
        "PICTURE_UPDATE": "Update picture",
 | 
			
		||||
        "PREFERENCES_EDITION": "Preferences edition",
 | 
			
		||||
        "PRIVACY-POLICY_EDITION": "Privacy policy",
 | 
			
		||||
        "PROFILE_EDITION": "Profile edition",
 | 
			
		||||
        "REGISTRATION_DATE": "Registration date",
 | 
			
		||||
        "SPORT": {
 | 
			
		||||
@@ -89,6 +92,7 @@
 | 
			
		||||
            "APPS": "apps",
 | 
			
		||||
            "PICTURE": "picture",
 | 
			
		||||
            "PREFERENCES": "preferences",
 | 
			
		||||
            "PRIVACY-POLICY": "privacy policy",
 | 
			
		||||
            "PROFILE": "profile",
 | 
			
		||||
            "SPORTS": "sports"
 | 
			
		||||
        },
 | 
			
		||||
@@ -104,9 +108,11 @@
 | 
			
		||||
    "REGISTER_DISABLED": "Sorry, registration is disabled.",
 | 
			
		||||
    "RESENT_ACCOUNT_CONFIRMATION": "Resend account confirmation email",
 | 
			
		||||
    "RESET_PASSWORD": "Reset your password",
 | 
			
		||||
    "REVIEW": "review",
 | 
			
		||||
    "SHOW_PASSWORD": "show password",
 | 
			
		||||
    "THIS_USER_ACCOUNT_IS_INACTIVE": "This user account is inactive.",
 | 
			
		||||
    "USERNAME": "Username",
 | 
			
		||||
    "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}."
 | 
			
		||||
}
 | 
			
		||||
@@ -10,8 +10,10 @@
 | 
			
		||||
    "ENTER_PASSWORD": "Saisissez un mot de passe",
 | 
			
		||||
    "FILTER_ON_USERNAME": "Filtrer sur le nom d'utilisateur",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "LANGUAGE": "Langue",
 | 
			
		||||
    "LAST_PRIVACY_POLICY_TO_VALIDATE": "La politique de confidentialité a été mise à jour. Veuillez l'{0} avant de poursuivre.",
 | 
			
		||||
    "LOGIN": "Se connecter",
 | 
			
		||||
    "LOGOUT": "Se déconnecter",
 | 
			
		||||
    "LOG_IN": "connecter",
 | 
			
		||||
@@ -68,6 +70,7 @@
 | 
			
		||||
        "PICTURE_REMOVE": "Supprimer",
 | 
			
		||||
        "PICTURE_UPDATE": "Mettre à jour l'image",
 | 
			
		||||
        "PREFERENCES_EDITION": "Mise à jour des préférences",
 | 
			
		||||
        "PRIVACY-POLICY_EDITION": "Politique de confidentialité",
 | 
			
		||||
        "PROFILE_EDITION": "Mise à jour du profil",
 | 
			
		||||
        "REGISTRATION_DATE": "Date d'inscription",
 | 
			
		||||
        "SPORT": {
 | 
			
		||||
@@ -89,6 +92,7 @@
 | 
			
		||||
            "APPS": "apps",
 | 
			
		||||
            "PICTURE": "image",
 | 
			
		||||
            "PREFERENCES": "préférences",
 | 
			
		||||
            "PRIVACY-POLICY": "politique de confidentialité",
 | 
			
		||||
            "PROFILE": "profil",
 | 
			
		||||
            "SPORTS": "sports"
 | 
			
		||||
        },
 | 
			
		||||
@@ -104,9 +108,11 @@
 | 
			
		||||
    "REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.",
 | 
			
		||||
    "RESENT_ACCOUNT_CONFIRMATION": "Envoyer à nouveau le courriel de confirmation de compte",
 | 
			
		||||
    "RESET_PASSWORD": "Réinitialiser votre mot de passe",
 | 
			
		||||
    "REVIEW": "accepter",
 | 
			
		||||
    "SHOW_PASSWORD": "afficher le mot de passe",
 | 
			
		||||
    "THIS_USER_ACCOUNT_IS_INACTIVE": "Le compte de cet utilisateur est inactif.",
 | 
			
		||||
    "USERNAME": "Nom d'utilisateur",
 | 
			
		||||
    "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}."
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@ import UserAccountEdition from '@/components/User/ProfileEdition/UserAccountEdit
 | 
			
		||||
import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue'
 | 
			
		||||
import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.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 AuthorizeUserApp from '@/components/User/UserApps/AuthorizeUserApp.vue'
 | 
			
		||||
import UserApps from '@/components/User/UserApps/index.vue'
 | 
			
		||||
@@ -219,6 +220,11 @@ const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
            component: UserSportPreferences,
 | 
			
		||||
            props: { isEdition: true },
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'privacy-policy',
 | 
			
		||||
            name: 'UserPrivacyPolicy',
 | 
			
		||||
            component: UserPrivacyPolicyValidation,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
 
 | 
			
		||||
@@ -427,4 +427,24 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
 | 
			
		||||
      })
 | 
			
		||||
      .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))
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
export enum AuthUserActions {
 | 
			
		||||
  ACCEPT_PRIVACY_POLICY = 'ACCEPT_PRIVACY_POLICY',
 | 
			
		||||
  CHECK_AUTH_USER = 'CHECK_AUTH_USER',
 | 
			
		||||
  CONFIRM_ACCOUNT = 'CONFIRM_ACCOUNT',
 | 
			
		||||
  CONFIRM_EMAIL = 'CONFIRM_EMAIL',
 | 
			
		||||
 
 | 
			
		||||
@@ -110,6 +110,11 @@ export interface IAuthUserActions {
 | 
			
		||||
  [AUTH_USER_STORE.ACTIONS.DELETE_PICTURE](
 | 
			
		||||
    context: ActionContext<IAuthUserState, IRootState>
 | 
			
		||||
  ): void
 | 
			
		||||
 | 
			
		||||
  [AUTH_USER_STORE.ACTIONS.ACCEPT_PRIVACY_POLICY](
 | 
			
		||||
    context: ActionContext<IAuthUserState, IRootState>,
 | 
			
		||||
    acceptedPolicy: boolean
 | 
			
		||||
  ): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IAuthUserGetters {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ export interface IUserProfile {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IAuthUserProfile extends IUserProfile {
 | 
			
		||||
  accepted_privacy_policy: boolean
 | 
			
		||||
  display_ascent: boolean
 | 
			
		||||
  imperial_units: boolean
 | 
			
		||||
  language: string | null
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,12 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="container privacy-policy-message"
 | 
			
		||||
      v-if="!authUser.accepted_privacy_policy"
 | 
			
		||||
    >
 | 
			
		||||
      <PrivacyPolicyToAccept />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
      <UserStatsCards :user="authUser" />
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -80,6 +86,7 @@
 | 
			
		||||
  import UserMonthStats from '@/components/Dashboard/UserMonthStats.vue'
 | 
			
		||||
  import UserRecords from '@/components/Dashboard/UserRecords/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 { ISport } from '@/types/sports'
 | 
			
		||||
  import { IAuthUserProfile } from '@/types/user'
 | 
			
		||||
@@ -126,6 +133,11 @@
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .privacy-policy-message {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @media screen and (max-width: $medium-limit) {
 | 
			
		||||
      padding-bottom: 60px;
 | 
			
		||||
      .dashboard-container {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user