Client - add password reset + refacto
This commit is contained in:
		@@ -22,7 +22,7 @@
 | 
			
		||||
    "register-service-worker": "^1.7.1",
 | 
			
		||||
    "vue": "^3.0.0",
 | 
			
		||||
    "vue-chart-3": "^0.5.8",
 | 
			
		||||
    "vue-i18n": "^9.1.0",
 | 
			
		||||
    "vue-i18n": "^9.1.9",
 | 
			
		||||
    "vue-router": "^4.0.0-0",
 | 
			
		||||
    "vuex": "^4.0.0-0"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,17 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="alert-message">
 | 
			
		||||
    <div v-html="t(message)" />
 | 
			
		||||
    <div v-html="$t(message)" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'AlertMessage',
 | 
			
		||||
    props: {
 | 
			
		||||
      message: String,
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,13 @@
 | 
			
		||||
      @input="updateText"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="remaining-chars">
 | 
			
		||||
      {{ t('workouts.REMAINING_CHARS') }}: {{ text.length }}/{{ charLimit }}
 | 
			
		||||
      {{ $t('workouts.REMAINING_CHARS') }}: {{ text.length }}/{{ charLimit }}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent, ref, watch } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'CustomTextarea',
 | 
			
		||||
@@ -40,7 +39,6 @@
 | 
			
		||||
    },
 | 
			
		||||
    emits: ['updateValue'],
 | 
			
		||||
    setup(props, { emit }) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      let text = ref('')
 | 
			
		||||
 | 
			
		||||
      function updateText(event: Event & { target: HTMLInputElement }) {
 | 
			
		||||
@@ -54,7 +52,7 @@
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      return { t, text, updateText }
 | 
			
		||||
      return { text, updateText }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,26 +2,21 @@
 | 
			
		||||
  <div class="error-message">
 | 
			
		||||
    <ul v-if="Array.isArray(message)">
 | 
			
		||||
      <li v-for="(subMessage, index) in message" :key="index">
 | 
			
		||||
        {{ t(subMessage) }}
 | 
			
		||||
        {{ $t(subMessage) }}
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <div v-else>{{ t(message) }}</div>
 | 
			
		||||
    <div v-else>{{ $t(message) }}</div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'ErrorMessage',
 | 
			
		||||
    props: {
 | 
			
		||||
      message: [String, Array],
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										63
									
								
								fittrackee_client/src/components/Common/Images/EmailSent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								fittrackee_client/src/components/Common/Images/EmailSent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg
 | 
			
		||||
    version="1.1"
 | 
			
		||||
    id="Capa_1"
 | 
			
		||||
    xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
    xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
    x="0px"
 | 
			
		||||
    y="0px"
 | 
			
		||||
    viewBox="0 0 345.834 345.834"
 | 
			
		||||
    style="enable-background: new 0 0 345.834 345.834"
 | 
			
		||||
    xml:space="preserve"
 | 
			
		||||
  >
 | 
			
		||||
    <g>
 | 
			
		||||
      <path
 | 
			
		||||
        d="M339.798,260.429c0.13-0.026,0.257-0.061,0.385-0.094c0.109-0.028,0.219-0.051,0.326-0.084
 | 
			
		||||
		c0.125-0.038,0.247-0.085,0.369-0.129c0.108-0.039,0.217-0.074,0.324-0.119c0.115-0.048,0.226-0.104,0.338-0.157
 | 
			
		||||
		c0.109-0.052,0.22-0.1,0.327-0.158c0.107-0.057,0.208-0.122,0.312-0.184c0.107-0.064,0.215-0.124,0.319-0.194
 | 
			
		||||
		c0.111-0.074,0.214-0.156,0.321-0.236c0.09-0.067,0.182-0.13,0.27-0.202c0.162-0.133,0.316-0.275,0.466-0.421
 | 
			
		||||
		c0.027-0.026,0.056-0.048,0.083-0.075c0.028-0.028,0.052-0.059,0.079-0.088c0.144-0.148,0.284-0.3,0.416-0.46
 | 
			
		||||
		c0.077-0.094,0.144-0.192,0.216-0.289c0.074-0.1,0.152-0.197,0.221-0.301c0.074-0.111,0.139-0.226,0.207-0.34
 | 
			
		||||
		c0.057-0.096,0.118-0.19,0.171-0.289c0.062-0.115,0.114-0.234,0.169-0.351c0.049-0.104,0.101-0.207,0.146-0.314
 | 
			
		||||
		c0.048-0.115,0.086-0.232,0.128-0.349c0.041-0.114,0.085-0.227,0.12-0.343c0.036-0.118,0.062-0.238,0.092-0.358
 | 
			
		||||
		c0.029-0.118,0.063-0.234,0.086-0.353c0.028-0.141,0.045-0.283,0.065-0.425c0.014-0.1,0.033-0.199,0.043-0.3
 | 
			
		||||
		c0.025-0.249,0.038-0.498,0.038-0.748V92.76c0-4.143-3.357-7.5-7.5-7.5h-236.25c-0.066,0-0.13,0.008-0.196,0.01
 | 
			
		||||
		c-0.143,0.004-0.285,0.01-0.427,0.022c-0.113,0.009-0.225,0.022-0.337,0.037c-0.128,0.016-0.255,0.035-0.382,0.058
 | 
			
		||||
		c-0.119,0.021-0.237,0.046-0.354,0.073c-0.119,0.028-0.238,0.058-0.356,0.092c-0.117,0.033-0.232,0.069-0.346,0.107
 | 
			
		||||
		c-0.117,0.04-0.234,0.082-0.349,0.128c-0.109,0.043-0.216,0.087-0.322,0.135c-0.118,0.053-0.235,0.11-0.351,0.169
 | 
			
		||||
		c-0.099,0.051-0.196,0.103-0.292,0.158c-0.116,0.066-0.23,0.136-0.343,0.208c-0.093,0.06-0.184,0.122-0.274,0.185
 | 
			
		||||
		c-0.106,0.075-0.211,0.153-0.314,0.235c-0.094,0.075-0.186,0.152-0.277,0.231c-0.09,0.079-0.179,0.158-0.266,0.242
 | 
			
		||||
		c-0.099,0.095-0.194,0.194-0.288,0.294c-0.047,0.05-0.097,0.094-0.142,0.145c-0.027,0.03-0.048,0.063-0.074,0.093
 | 
			
		||||
		c-0.094,0.109-0.182,0.223-0.27,0.338c-0.064,0.084-0.13,0.168-0.19,0.254c-0.078,0.112-0.15,0.227-0.222,0.343
 | 
			
		||||
		c-0.059,0.095-0.12,0.189-0.174,0.286c-0.063,0.112-0.118,0.227-0.175,0.342c-0.052,0.105-0.106,0.21-0.153,0.317
 | 
			
		||||
		c-0.049,0.113-0.092,0.23-0.135,0.345c-0.043,0.113-0.087,0.225-0.124,0.339c-0.037,0.115-0.067,0.232-0.099,0.349
 | 
			
		||||
		c-0.032,0.12-0.066,0.239-0.093,0.36c-0.025,0.113-0.042,0.228-0.062,0.342c-0.022,0.13-0.044,0.26-0.06,0.39
 | 
			
		||||
		c-0.013,0.108-0.019,0.218-0.027,0.328c-0.01,0.14-0.019,0.28-0.021,0.421c-0.001,0.041-0.006,0.081-0.006,0.122v46.252
 | 
			
		||||
		c0,4.143,3.357,7.5,7.5,7.5s7.5-3.357,7.5-7.5v-29.595l66.681,59.037c-0.348,0.245-0.683,0.516-0.995,0.827l-65.687,65.687v-49.288
 | 
			
		||||
		c0-4.143-3.357-7.5-7.5-7.5s-7.5,3.357-7.5,7.5v9.164h-38.75c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5,7.5,7.5h38.75v43.231
 | 
			
		||||
		c0,4.143,3.357,7.5,7.5,7.5h236.25c0.247,0,0.494-0.013,0.74-0.037c0.115-0.011,0.226-0.033,0.339-0.049
 | 
			
		||||
		C339.542,260.469,339.67,260.454,339.798,260.429z M330.834,234.967l-65.688-65.687c-0.042-0.042-0.087-0.077-0.13-0.117
 | 
			
		||||
		l49.383-41.897c3.158-2.68,3.546-7.412,0.866-10.571c-2.678-3.157-7.41-3.547-10.571-0.866l-84.381,71.59l-98.444-87.158h208.965
 | 
			
		||||
		V234.967z M185.878,179.888c0.535-0.535,0.969-1.131,1.308-1.765l28.051,24.835c1.418,1.255,3.194,1.885,4.972,1.885
 | 
			
		||||
		c1.726,0,3.451-0.593,4.853-1.781l28.587-24.254c0.26,0.38,0.553,0.743,0.89,1.08l65.687,65.687H120.191L185.878,179.888z"
 | 
			
		||||
      />
 | 
			
		||||
      <path
 | 
			
		||||
        d="M7.5,170.676h126.667c4.143,0,7.5-3.357,7.5-7.5s-3.357-7.5-7.5-7.5H7.5c-4.143,0-7.5,3.357-7.5,7.5
 | 
			
		||||
		S3.357,170.676,7.5,170.676z"
 | 
			
		||||
      />
 | 
			
		||||
      <path
 | 
			
		||||
        d="M20.625,129.345H77.5c4.143,0,7.5-3.357,7.5-7.5s-3.357-7.5-7.5-7.5H20.625c-4.143,0-7.5,3.357-7.5,7.5
 | 
			
		||||
		S16.482,129.345,20.625,129.345z"
 | 
			
		||||
      />
 | 
			
		||||
      <path
 | 
			
		||||
        d="M62.5,226.51h-55c-4.143,0-7.5,3.357-7.5,7.5s3.357,7.5,7.5,7.5h55c4.143,0,7.5-3.357,7.5-7.5S66.643,226.51,62.5,226.51z"
 | 
			
		||||
      />
 | 
			
		||||
    </g>
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'EmailSent',
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										94
									
								
								fittrackee_client/src/components/Common/Images/Password.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								fittrackee_client/src/components/Common/Images/Password.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <svg
 | 
			
		||||
    version="1.1"
 | 
			
		||||
    id="Layer_1"
 | 
			
		||||
    xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
    xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
			
		||||
    x="0px"
 | 
			
		||||
    y="0px"
 | 
			
		||||
    viewBox="0 0 512.001 512.001"
 | 
			
		||||
    style="enable-background: new 0 0 512.001 512.001"
 | 
			
		||||
    xml:space="preserve"
 | 
			
		||||
  >
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M468.683,287.265h-69.07c-4.147,0-7.508,3.361-7.508,7.508c0,4.147,3.361,7.508,7.508,7.508h69.07
 | 
			
		||||
			c4.147,0,7.508-3.361,7.508-7.508C476.191,290.626,472.83,287.265,468.683,287.265z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M105.012,268.377L85.781,256l19.231-12.376c3.487-2.243,4.495-6.888,2.251-10.376c-2.244-3.486-6.888-4.497-10.376-2.25
 | 
			
		||||
			l-17.471,11.243v-20.776c0-4.147-3.361-7.508-7.508-7.508c-4.147,0-7.508,3.361-7.508,7.508v20.775l-17.47-11.243
 | 
			
		||||
			c-3.486-2.245-8.132-1.238-10.376,2.25c-2.245,3.487-1.237,8.133,2.25,10.376L58.034,256l-19.231,12.376
 | 
			
		||||
			c-3.487,2.244-4.495,6.889-2.25,10.376c1.435,2.23,3.852,3.446,6.32,3.446c1.391,0,2.799-0.386,4.056-1.196l17.47-11.243v20.775
 | 
			
		||||
			c0,4.147,3.361,7.508,7.508,7.508c4.147,0,7.508-3.361,7.508-7.508V269.76l17.471,11.243c1.257,0.809,2.664,1.196,4.056,1.196
 | 
			
		||||
			c2.467,0,4.885-1.216,6.32-3.446C109.507,275.266,108.499,270.62,105.012,268.377z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M194.441,268.377L175.21,256l19.231-12.376c3.487-2.244,4.495-6.889,2.25-10.376c-2.245-3.486-6.888-4.497-10.376-2.25
 | 
			
		||||
			l-17.47,11.243v-20.775c0-4.147-3.361-7.508-7.508-7.508c-4.147,0-7.508,3.361-7.508,7.508v20.776l-17.471-11.243
 | 
			
		||||
			c-3.487-2.245-8.133-1.238-10.376,2.25c-2.245,3.487-1.237,8.133,2.25,10.376L147.463,256l-19.231,12.376
 | 
			
		||||
			c-3.487,2.244-4.495,6.889-2.25,10.376c1.435,2.23,3.852,3.446,6.32,3.446c1.391,0,2.799-0.386,4.056-1.196l17.471-11.243v20.776
 | 
			
		||||
			c0,4.147,3.361,7.508,7.508,7.508c4.147,0,7.508-3.361,7.508-7.508V269.76l17.47,11.243c1.257,0.809,2.664,1.196,4.056,1.196
 | 
			
		||||
			c2.467,0,4.885-1.216,6.32-3.446C198.936,275.266,197.928,270.62,194.441,268.377z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M283.871,268.377L264.64,256l19.231-12.376c3.487-2.243,4.495-6.888,2.251-10.376c-2.245-3.486-6.888-4.497-10.376-2.25
 | 
			
		||||
			l-17.471,11.243v-20.775c0-4.147-3.361-7.508-7.508-7.508c-4.147,0-7.508,3.361-7.508,7.508v20.775l-17.471-11.243
 | 
			
		||||
			c-3.486-2.245-8.134-1.238-10.376,2.25c-2.245,3.487-1.237,8.133,2.25,10.376L236.892,256l-19.231,12.376
 | 
			
		||||
			c-3.487,2.244-4.495,6.889-2.25,10.376c1.435,2.23,3.852,3.446,6.32,3.446c1.391,0,2.799-0.386,4.056-1.196l17.471-11.243v20.775
 | 
			
		||||
			c0,4.147,3.361,7.508,7.508,7.508c4.147,0,7.508-3.361,7.508-7.508V269.76l17.471,11.243c1.257,0.809,2.664,1.196,4.056,1.196
 | 
			
		||||
			c2.467,0,4.886-1.216,6.32-3.446C288.366,275.266,287.358,270.62,283.871,268.377z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M373.3,268.377L354.069,256l19.231-12.376c3.487-2.244,4.495-6.889,2.25-10.376c-2.244-3.486-6.888-4.497-10.376-2.25
 | 
			
		||||
			l-17.471,11.243v-20.776c0-4.147-3.361-7.508-7.508-7.508c-4.147,0-7.508,3.361-7.508,7.508v20.775l-17.47-11.243
 | 
			
		||||
			c-3.486-2.245-8.132-1.238-10.376,2.25c-2.245,3.487-1.237,8.133,2.25,10.376L326.322,256l-19.231,12.376
 | 
			
		||||
			c-3.487,2.244-4.495,6.889-2.25,10.376c1.435,2.23,3.852,3.446,6.32,3.446c1.391,0,2.799-0.386,4.056-1.196l17.47-11.243v20.776
 | 
			
		||||
			c0,4.147,3.361,7.508,7.508,7.508c4.147,0,7.508-3.361,7.508-7.508V269.76l17.471,11.243c1.257,0.809,2.664,1.196,4.056,1.196
 | 
			
		||||
			c2.467,0,4.885-1.216,6.32-3.446C377.795,275.266,376.787,270.62,373.3,268.377z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M271.792,330.359H15.016V181.642h93.1c4.147,0,7.508-3.361,7.508-7.508c0-4.147-3.361-7.508-7.508-7.508H12.513
 | 
			
		||||
			C5.613,166.626,0,172.24,0,179.14v153.722c0,6.9,5.613,12.513,12.513,12.513h259.278c4.147,0,7.508-3.361,7.508-7.508
 | 
			
		||||
			C279.299,333.72,275.939,330.359,271.792,330.359z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g>
 | 
			
		||||
      <g>
 | 
			
		||||
        <path
 | 
			
		||||
          d="M499.487,166.626H162.174c-4.147,0-7.508,3.361-7.508,7.508c0,4.147,3.361,7.508,7.508,7.508h334.811v148.716H323.848
 | 
			
		||||
			c-4.147,0-7.508,3.361-7.508,7.508c0,4.147,3.361,7.508,7.508,7.508h175.64c6.9,0,12.513-5.613,12.513-12.513V179.14
 | 
			
		||||
			C512.001,172.24,506.387,166.626,499.487,166.626z"
 | 
			
		||||
        />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'Password',
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
@@ -17,12 +17,12 @@
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import CyclingSport from '@/components/Common/SportImage/CyclingSport.vue'
 | 
			
		||||
  import CyclingTransport from '@/components/Common/SportImage/CyclingTransport.vue'
 | 
			
		||||
  import Hiking from '@/components/Common/SportImage/Hiking.vue'
 | 
			
		||||
  import MountainBiking from '@/components/Common/SportImage/MountainBiking.vue'
 | 
			
		||||
  import Running from '@/components/Common/SportImage/Running.vue'
 | 
			
		||||
  import Walking from '@/components/Common/SportImage/Walking.vue'
 | 
			
		||||
  import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue'
 | 
			
		||||
  import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue'
 | 
			
		||||
  import Hiking from '@/components/Common/Images/SportImage/Hiking.vue'
 | 
			
		||||
  import MountainBiking from '@/components/Common/Images/SportImage/MountainBiking.vue'
 | 
			
		||||
  import Running from '@/components/Common/Images/SportImage/Running.vue'
 | 
			
		||||
  import Walking from '@/components/Common/Images/SportImage/Walking.vue'
 | 
			
		||||
  import { sportColors } from '@/utils/sports'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
@@ -10,10 +10,10 @@
 | 
			
		||||
          <ErrorMessage :message="errorMessages" v-if="errorMessages" />
 | 
			
		||||
          <div class="modal-buttons">
 | 
			
		||||
            <button class="confirm" @click="emit('confirmAction')">
 | 
			
		||||
              {{ t('buttons.YES') }}
 | 
			
		||||
              {{ $t('buttons.YES') }}
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="cancel" @click="emit('cancelAction')">
 | 
			
		||||
              {{ t('buttons.NO') }}
 | 
			
		||||
              {{ $t('buttons.NO') }}
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { ComputedRef, computed, defineComponent, onUnmounted } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import ErrorMessage from '@/components/Common/ErrorMessage.vue'
 | 
			
		||||
@@ -49,13 +48,12 @@
 | 
			
		||||
    },
 | 
			
		||||
    emits: ['cancelAction', 'confirmAction'],
 | 
			
		||||
    setup(props, { emit }) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const errorMessages: ComputedRef<string | string[] | null> = computed(
 | 
			
		||||
        () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
 | 
			
		||||
      )
 | 
			
		||||
      onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
 | 
			
		||||
      return { errorMessages, t, emit }
 | 
			
		||||
      return { errorMessages, emit }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <Error
 | 
			
		||||
    title="404"
 | 
			
		||||
    :message="t(`error.NOT_FOUND.${target}`)"
 | 
			
		||||
    :message="$t(`error.NOT_FOUND.${target}`)"
 | 
			
		||||
    :button-text="t('common.HOME')"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Error from '@/components/Common/Error.vue'
 | 
			
		||||
 | 
			
		||||
@@ -23,9 +22,5 @@
 | 
			
		||||
        default: 'PAGE',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="start-chart">
 | 
			
		||||
    <div v-if="hideChartIfNoData && emptyStats">
 | 
			
		||||
      {{ t('workouts.NO_WORKOUTS') }}
 | 
			
		||||
      {{ $t('workouts.NO_WORKOUTS') }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else>
 | 
			
		||||
      <div class="chart-radio">
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
            :checked="displayedData === 'total_distance'"
 | 
			
		||||
            @click="updateDisplayData"
 | 
			
		||||
          />
 | 
			
		||||
          {{ t('workouts.DISTANCE') }}
 | 
			
		||||
          {{ $t('workouts.DISTANCE') }}
 | 
			
		||||
        </label>
 | 
			
		||||
        <label>
 | 
			
		||||
          <input
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
            :checked="displayedData === 'total_duration'"
 | 
			
		||||
            @click="updateDisplayData"
 | 
			
		||||
          />
 | 
			
		||||
          {{ t('workouts.DURATION') }}
 | 
			
		||||
          {{ $t('workouts.DURATION') }}
 | 
			
		||||
        </label>
 | 
			
		||||
        <label>
 | 
			
		||||
          <input
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
            :checked="displayedData === 'nb_workouts'"
 | 
			
		||||
            @click="updateDisplayData"
 | 
			
		||||
          />
 | 
			
		||||
          {{ t('workouts.WORKOUT', 2) }}
 | 
			
		||||
          {{ $t('workouts.WORKOUT', 2) }}
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <Chart
 | 
			
		||||
@@ -57,7 +57,6 @@
 | 
			
		||||
    watch,
 | 
			
		||||
    onBeforeMount,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Chart from '@/components/Common/StatsChart/Chart.vue'
 | 
			
		||||
  import { STATS_STORE } from '@/store/constants'
 | 
			
		||||
@@ -106,7 +105,6 @@
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
 | 
			
		||||
      let displayedData: Ref<TStatisticsDatasetKeys> = ref('total_distance')
 | 
			
		||||
      const statistics: ComputedRef<TStatisticsFromApi> = computed(
 | 
			
		||||
@@ -168,7 +166,6 @@
 | 
			
		||||
        labels: computed(() => formattedStats.value.labels),
 | 
			
		||||
        emptyStats: computed(() => Object.keys(statistics.value).length === 0),
 | 
			
		||||
        displayedData,
 | 
			
		||||
        t,
 | 
			
		||||
        updateDisplayData,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="timeline">
 | 
			
		||||
    <div class="section-title">{{ t('workouts.LATEST_WORKOUTS') }}</div>
 | 
			
		||||
    <div class="section-title">{{ $t('workouts.LATEST_WORKOUTS') }}</div>
 | 
			
		||||
    <div v-if="user.nb_workouts > 0 && workouts.length === 0">
 | 
			
		||||
      <WorkoutCard
 | 
			
		||||
        v-for="index in [...Array(initWorkoutsCount).keys()]"
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
      <NoWorkouts v-if="workouts.length === 0" />
 | 
			
		||||
      <div v-if="moreWorkoutsExist" class="more-workouts">
 | 
			
		||||
        <button @click="loadMoreWorkouts">
 | 
			
		||||
          {{ t('workouts.LOAD_MORE_WORKOUT') }}
 | 
			
		||||
          {{ $t('workouts.LOAD_MORE_WORKOUT') }}
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -39,7 +39,6 @@
 | 
			
		||||
    ref,
 | 
			
		||||
    onBeforeMount,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import WorkoutCard from '@/components/Workout/WorkoutCard.vue'
 | 
			
		||||
  import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
 | 
			
		||||
@@ -67,7 +66,6 @@
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
 | 
			
		||||
      let page = ref(1)
 | 
			
		||||
      const per_page = 5
 | 
			
		||||
@@ -103,7 +101,6 @@
 | 
			
		||||
        moreWorkoutsExist,
 | 
			
		||||
        per_page,
 | 
			
		||||
        workouts,
 | 
			
		||||
        t,
 | 
			
		||||
        loadMoreWorkouts,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
  import { defineComponent, PropType } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import { IWorkout } from '@/types/workouts'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { endOfMonth, startOfMonth } from 'date-fns'
 | 
			
		||||
  import { PropType, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import StatChart from '@/components/Common/StatsChart/index.vue'
 | 
			
		||||
@@ -42,7 +41,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const date = new Date()
 | 
			
		||||
      return {
 | 
			
		||||
        chartParams: {
 | 
			
		||||
@@ -51,7 +49,6 @@
 | 
			
		||||
          end: endOfMonth(date),
 | 
			
		||||
        },
 | 
			
		||||
        selectedSportIds: props.sports.map((sport) => sport.id),
 | 
			
		||||
        t,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import { IRecord } from '@/types/workouts'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,11 @@
 | 
			
		||||
  <div class="user-records-section">
 | 
			
		||||
    <div class="section-title">
 | 
			
		||||
      <i class="fa fa-trophy custom-fa-small" aria-hidden="true" />
 | 
			
		||||
      {{ t('workouts.RECORD', 2) }}
 | 
			
		||||
      {{ $t('workouts.RECORD', 2) }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="user-records">
 | 
			
		||||
      <div v-if="Object.keys(recordsBySport).length === 0" class="no-records">
 | 
			
		||||
        {{ t('workouts.NO_RECORDS') }}
 | 
			
		||||
        {{ $t('workouts.NO_RECORDS') }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <RecordsCard
 | 
			
		||||
        v-for="sportTranslatedLabel in Object.keys(recordsBySport).sort()"
 | 
			
		||||
@@ -52,7 +52,7 @@
 | 
			
		||||
          props.user.timezone
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
      return { recordsBySport, t }
 | 
			
		||||
      return { recordsBySport }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
    <UserStatCard
 | 
			
		||||
      icon="calendar"
 | 
			
		||||
      :value="user.nb_workouts"
 | 
			
		||||
      :text="t('workouts.WORKOUT', user.nb_workouts)"
 | 
			
		||||
      :text="$t('workouts.WORKOUT', user.nb_workouts)"
 | 
			
		||||
    />
 | 
			
		||||
    <UserStatCard
 | 
			
		||||
      icon="road"
 | 
			
		||||
      :value="Number(user.total_distance).toFixed(2)"
 | 
			
		||||
      :text="t('workouts.KM')"
 | 
			
		||||
      :text="$t('workouts.KM')"
 | 
			
		||||
    />
 | 
			
		||||
    <UserStatCard
 | 
			
		||||
      icon="clock-o"
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
    <UserStatCard
 | 
			
		||||
      icon="tags"
 | 
			
		||||
      :value="user.nb_sports"
 | 
			
		||||
      :text="t('workouts.SPORT', user.nb_sports)"
 | 
			
		||||
      :text="$t('workouts.SPORT', user.nb_sports)"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -63,10 +63,7 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        t,
 | 
			
		||||
        total_duration: computed(() => get_duration(total_duration)),
 | 
			
		||||
      }
 | 
			
		||||
      return { total_duration: computed(() => get_duration(total_duration)) }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,19 +21,19 @@
 | 
			
		||||
        <div class="nav-items-app-menu" @click="closeMenu()">
 | 
			
		||||
          <div class="nav-items-group" v-if="isAuthenticated">
 | 
			
		||||
            <router-link class="nav-item" to="/">{{
 | 
			
		||||
              t('dashboard.DASHBOARD')
 | 
			
		||||
              $t('dashboard.DASHBOARD')
 | 
			
		||||
            }}</router-link>
 | 
			
		||||
            <router-link class="nav-item" to="/workouts">
 | 
			
		||||
              {{ capitalize(t('workouts.WORKOUT', 2)) }}
 | 
			
		||||
              {{ capitalize($t('workouts.WORKOUT', 2)) }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <router-link class="nav-item" to="/statistics">
 | 
			
		||||
              {{ t('statistics.STATISTICS') }}
 | 
			
		||||
              {{ $t('statistics.STATISTICS') }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <div v-if="isAuthenticated && authUser.admin" class="nav-item">
 | 
			
		||||
              {{ t('administration.ADMIN') }}
 | 
			
		||||
              {{ $t('administration.ADMIN') }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <router-link class="nav-item" to="/workouts/add">
 | 
			
		||||
              {{ t('workouts.ADD_WORKOUT') }}
 | 
			
		||||
              {{ $t('workouts.ADD_WORKOUT') }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <div class="nav-item nav-separator" />
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -47,15 +47,15 @@
 | 
			
		||||
              {{ authUser.username }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <div class="nav-item nav-link" @click="logout">
 | 
			
		||||
              {{ t('user.LOGOUT') }}
 | 
			
		||||
              {{ $t('user.LOGOUT') }}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="nav-items-group" v-else>
 | 
			
		||||
            <router-link class="nav-item" to="/login" @click="closeMenu">{{
 | 
			
		||||
              t('user.LOGIN')
 | 
			
		||||
              $t('user.LOGIN')
 | 
			
		||||
            }}</router-link>
 | 
			
		||||
            <router-link class="nav-item" to="/register" @click="closeMenu">{{
 | 
			
		||||
              t('user.REGISTER')
 | 
			
		||||
              $t('user.REGISTER')
 | 
			
		||||
            }}</router-link>
 | 
			
		||||
          </div>
 | 
			
		||||
          <Dropdown
 | 
			
		||||
@@ -93,7 +93,7 @@
 | 
			
		||||
    },
 | 
			
		||||
    emits: ['menuInteraction'],
 | 
			
		||||
    setup(props, { emit }) {
 | 
			
		||||
      const { t, locale, availableLocales } = useI18n()
 | 
			
		||||
      const { locale, availableLocales } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
 | 
			
		||||
      const availableLanguages = availableLocales.map((l) => {
 | 
			
		||||
@@ -140,7 +140,6 @@
 | 
			
		||||
        isAuthenticated,
 | 
			
		||||
        isMenuOpen,
 | 
			
		||||
        language,
 | 
			
		||||
        t,
 | 
			
		||||
        capitalize,
 | 
			
		||||
        openMenu,
 | 
			
		||||
        closeMenu,
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@
 | 
			
		||||
            stroke: none;
 | 
			
		||||
            fill-rule: nonzero;
 | 
			
		||||
            fill: var(--app-color);
 | 
			
		||||
            filter: drop-shadow(10px 10px 10px var(--app-shadow-color));
 | 
			
		||||
            filter: var(--svg-filter);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
              :checked="selectedTimeFrame === frame"
 | 
			
		||||
              @input="onUpdateTimeFrame(frame)"
 | 
			
		||||
            />
 | 
			
		||||
            <span>{{ t(`statistics.TIME_FRAMES.${frame}`) }}</span>
 | 
			
		||||
            <span>{{ $t(`statistics.TIME_FRAMES.${frame}`) }}</span>
 | 
			
		||||
          </label>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -39,13 +39,11 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent, ref } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'StatsMenu',
 | 
			
		||||
    emits: ['arrowClick', 'timeFrameUpdate'],
 | 
			
		||||
    setup(props, { emit }) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      let selectedTimeFrame = ref('month')
 | 
			
		||||
      const timeFrames = ['week', 'month', 'year']
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +54,6 @@
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        selectedTimeFrame,
 | 
			
		||||
        t,
 | 
			
		||||
        timeFrames,
 | 
			
		||||
        onUpdateTimeFrame,
 | 
			
		||||
        emit,
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
  import { ComputedRef, PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import { ISport, ITranslatedSport } from '@/types/sports'
 | 
			
		||||
  import { translateSports, sportColors } from '@/utils/sports'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,6 @@
 | 
			
		||||
        chartParams,
 | 
			
		||||
        selectedTimeFrame,
 | 
			
		||||
        sportColors,
 | 
			
		||||
        t,
 | 
			
		||||
        timeFrames,
 | 
			
		||||
        translatedSports,
 | 
			
		||||
        selectedSportIds,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="password-action-done">
 | 
			
		||||
    <EmailSent v-if="action === 'request-sent'" />
 | 
			
		||||
    <Password v-else />
 | 
			
		||||
    <div class="password-message">
 | 
			
		||||
      <span v-if="action === 'request-sent'"
 | 
			
		||||
        >{{ $t('user.PASSWORD_SENT_EMAIL_TEXT') }}
 | 
			
		||||
      </span>
 | 
			
		||||
      <i18n-t v-else keypath="user.PASSWORD_UPDATED">
 | 
			
		||||
        <router-link to="/login">
 | 
			
		||||
          {{ $t('common.HERE') }}
 | 
			
		||||
        </router-link>
 | 
			
		||||
      </i18n-t>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
 | 
			
		||||
  import EmailSent from '@/components/Common/Images/EmailSent.vue'
 | 
			
		||||
  import Password from '@/components/Common/Images/Password.vue'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'PasswordActionDone',
 | 
			
		||||
    components: {
 | 
			
		||||
      EmailSent,
 | 
			
		||||
      Password,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
      action: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
  @import '~@/scss/base';
 | 
			
		||||
 | 
			
		||||
  #password-action-done {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    margin: 100px auto;
 | 
			
		||||
    width: 700px;
 | 
			
		||||
    @media screen and (max-width: $medium-limit) {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    svg {
 | 
			
		||||
      stroke: none;
 | 
			
		||||
      fill-rule: nonzero;
 | 
			
		||||
      fill: var(--app-color);
 | 
			
		||||
      filter: var(--svg-filter);
 | 
			
		||||
      width: 100px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .password-message {
 | 
			
		||||
      font-size: 1.1em;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
 | 
			
		||||
      @media screen and (max-width: $medium-limit) {
 | 
			
		||||
        font-size: 1em;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="password-reset-request">
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{ $t('user.RESET_PASSWORD') }}</template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        <UserAuthForm :action="action" :token="token" />
 | 
			
		||||
      </template>
 | 
			
		||||
    </Card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import UserAuthForm from '@/components/User/UserAuthForm.vue'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'PasswordResetForm',
 | 
			
		||||
    components: {
 | 
			
		||||
      Card,
 | 
			
		||||
      UserAuthForm,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
      action: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
      token: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
  @import '~@/scss/base';
 | 
			
		||||
 | 
			
		||||
  #password-reset-request {
 | 
			
		||||
    margin: 100px auto;
 | 
			
		||||
    width: 700px;
 | 
			
		||||
    @media screen and (max-width: $medium-limit) {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ::v-deep(.card) {
 | 
			
		||||
      .card-content {
 | 
			
		||||
        #user-form {
 | 
			
		||||
          width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,36 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="user-infos" class="description-list">
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
 | 
			
		||||
      <dd>{{ registrationDate }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.FIRST_NAME') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.FIRST_NAME') }}:</dt>
 | 
			
		||||
      <dd>{{ user.first_name }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.LAST_NAME') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.LAST_NAME') }}:</dt>
 | 
			
		||||
      <dd>{{ user.last_name }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.BIRTH_DATE') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.BIRTH_DATE') }}:</dt>
 | 
			
		||||
      <dd>{{ birthDate }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.LOCATION') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.LOCATION') }}:</dt>
 | 
			
		||||
      <dd>{{ user.location }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.BIO') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.BIO') }}:</dt>
 | 
			
		||||
      <dd class="user-bio">
 | 
			
		||||
        {{ user.bio }}
 | 
			
		||||
      </dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <div class="profile-buttons">
 | 
			
		||||
      <button @click="$router.push('/profile/edit')">
 | 
			
		||||
        {{ t('user.PROFILE.EDIT') }}
 | 
			
		||||
        {{ $t('user.PROFILE.EDIT') }}
 | 
			
		||||
      </button>
 | 
			
		||||
      <button @click="$router.push('/')">{{ t('common.HOME') }}</button>
 | 
			
		||||
      <button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -38,7 +38,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { format } from 'date-fns'
 | 
			
		||||
  import { PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import { IAuthUserProfile } from '@/types/user'
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +50,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const registrationDate = computed(() =>
 | 
			
		||||
        props.user.created_at
 | 
			
		||||
          ? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
 | 
			
		||||
@@ -62,7 +60,7 @@
 | 
			
		||||
          ? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
 | 
			
		||||
          : ''
 | 
			
		||||
      )
 | 
			
		||||
      return { birthDate, registrationDate, t }
 | 
			
		||||
      return { birthDate, registrationDate }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="user-preferences" class="description-list">
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.LANGUAGE') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.LANGUAGE') }}:</dt>
 | 
			
		||||
      <dd>{{ language }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.TIMEZONE') }}:</dt>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.TIMEZONE') }}:</dt>
 | 
			
		||||
      <dd>{{ timezone }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <dl>
 | 
			
		||||
      <dt>{{ t('user.PROFILE.FIRST_DAY_OF_WEEK') }}:</dt>
 | 
			
		||||
      <dd>{{ t(`user.PROFILE.${fistDayOfWeek}`) }}</dd>
 | 
			
		||||
      <dt>{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}:</dt>
 | 
			
		||||
      <dd>{{ $t(`user.PROFILE.${fistDayOfWeek}`) }}</dd>
 | 
			
		||||
    </dl>
 | 
			
		||||
    <div class="profile-buttons">
 | 
			
		||||
      <button @click="$router.push('/profile/edit/preferences')">
 | 
			
		||||
        {{ t('user.PROFILE.EDIT_PREFERENCES') }}
 | 
			
		||||
        {{ $t('user.PROFILE.EDIT_PREFERENCES') }}
 | 
			
		||||
      </button>
 | 
			
		||||
      <button @click="$router.push('/')">{{ t('common.HOME') }}</button>
 | 
			
		||||
      <button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import { IAuthUserProfile } from '@/types/user'
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +35,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const language = computed(() =>
 | 
			
		||||
        props.user.language ? props.user.language.toUpperCase() : 'EN'
 | 
			
		||||
      )
 | 
			
		||||
@@ -46,7 +44,7 @@
 | 
			
		||||
      const timezone = computed(() =>
 | 
			
		||||
        props.user.timezone ? props.user.timezone : 'Europe/Paris'
 | 
			
		||||
      )
 | 
			
		||||
      return { fistDayOfWeek, language, t, timezone }
 | 
			
		||||
      return { fistDayOfWeek, language, timezone }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
          <div class="user-stat">
 | 
			
		||||
            <span class="stat-number">{{ user.nb_workouts }}</span>
 | 
			
		||||
            <span class="stat-label">
 | 
			
		||||
              {{ t('workouts.WORKOUT', user.nb_workouts) }}
 | 
			
		||||
              {{ $t('workouts.WORKOUT', user.nb_workouts) }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="user-stat">
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
          <div class="user-stat hide-small">
 | 
			
		||||
            <span class="stat-number">{{ user.nb_sports }}</span>
 | 
			
		||||
            <span class="stat-label">
 | 
			
		||||
              {{ t('workouts.SPORT', user.nb_sports) }}
 | 
			
		||||
              {{ $t('workouts.SPORT', user.nb_sports) }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -36,7 +36,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { ComputedRef, PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import UserInfos from '@/components/User/ProfileDisplay/UserInfos.vue'
 | 
			
		||||
  import UserPreferences from '@/components/User/ProfileDisplay/UserPreferences.vue'
 | 
			
		||||
@@ -64,14 +63,13 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const tabs = ['PROFILE', 'PREFERENCES']
 | 
			
		||||
      const authUserPictureUrl: ComputedRef<string> = computed(() =>
 | 
			
		||||
        props.user.picture
 | 
			
		||||
          ? `${getApiUrl()}/users/${props.user.username}/picture?${Date.now()}`
 | 
			
		||||
          : ''
 | 
			
		||||
      )
 | 
			
		||||
      return { authUserPictureUrl, t, tabs }
 | 
			
		||||
      return { authUserPictureUrl, tabs }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
  <div id="user-infos-edition">
 | 
			
		||||
    <Modal
 | 
			
		||||
      v-if="displayModal"
 | 
			
		||||
      :title="t('common.CONFIRMATION')"
 | 
			
		||||
      :message="t('user.CONFIRM_ACCOUNT_DELETION')"
 | 
			
		||||
      :title="$t('common.CONFIRMATION')"
 | 
			
		||||
      :message="$t('user.CONFIRM_ACCOUNT_DELETION')"
 | 
			
		||||
      @confirmAction="deleteAccount(user.username)"
 | 
			
		||||
      @cancelAction="updateDisplayModal(false)"
 | 
			
		||||
    />
 | 
			
		||||
@@ -11,15 +11,15 @@
 | 
			
		||||
      <ErrorMessage :message="errorMessages" v-if="errorMessages" />
 | 
			
		||||
      <form @submit.prevent="updateProfile">
 | 
			
		||||
        <label class="form-items" for="email">
 | 
			
		||||
          {{ t('user.EMAIL') }}
 | 
			
		||||
          {{ $t('user.EMAIL') }}
 | 
			
		||||
          <input id="email" :value="user.email" disabled />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="registrationDate">
 | 
			
		||||
          {{ t('user.PROFILE.REGISTRATION_DATE') }}
 | 
			
		||||
          {{ $t('user.PROFILE.REGISTRATION_DATE') }}
 | 
			
		||||
          <input id="registrationDate" :value="registrationDate" disabled />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="password">
 | 
			
		||||
          {{ t('user.PASSWORD') }}
 | 
			
		||||
          {{ $t('user.PASSWORD') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="password"
 | 
			
		||||
            type="password"
 | 
			
		||||
@@ -28,7 +28,7 @@
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="passwordConfirmation">
 | 
			
		||||
          {{ t('user.PASSWORD_CONFIRMATION') }}
 | 
			
		||||
          {{ $t('user.PASSWORD_CONFIRMATION') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="passwordConfirmation"
 | 
			
		||||
            type="password"
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
        </label>
 | 
			
		||||
        <hr />
 | 
			
		||||
        <label class="form-items" for="first_name">
 | 
			
		||||
          {{ t('user.PROFILE.FIRST_NAME') }}
 | 
			
		||||
          {{ $t('user.PROFILE.FIRST_NAME') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="first_name"
 | 
			
		||||
            v-model="userForm.first_name"
 | 
			
		||||
@@ -46,11 +46,11 @@
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="last_name">
 | 
			
		||||
          {{ t('user.PROFILE.LAST_NAME') }}
 | 
			
		||||
          {{ $t('user.PROFILE.LAST_NAME') }}
 | 
			
		||||
          <input id="last_name" v-model="userForm.last_name" />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="birth_date">
 | 
			
		||||
          {{ t('user.PROFILE.BIRTH_DATE') }}
 | 
			
		||||
          {{ $t('user.PROFILE.BIRTH_DATE') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="birth_date"
 | 
			
		||||
            type="date"
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="location">
 | 
			
		||||
          {{ t('user.PROFILE.LOCATION') }}
 | 
			
		||||
          {{ $t('user.PROFILE.LOCATION') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="location"
 | 
			
		||||
            v-model="userForm.location"
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items">
 | 
			
		||||
          {{ t('user.PROFILE.BIO') }}
 | 
			
		||||
          {{ $t('user.PROFILE.BIO') }}
 | 
			
		||||
          <CustomTextArea
 | 
			
		||||
            name="bio"
 | 
			
		||||
            :charLimit="200"
 | 
			
		||||
@@ -79,13 +79,13 @@
 | 
			
		||||
        </label>
 | 
			
		||||
        <div class="form-buttons">
 | 
			
		||||
          <button class="confirm" type="submit">
 | 
			
		||||
            {{ t('buttons.SUBMIT') }}
 | 
			
		||||
            {{ $t('buttons.SUBMIT') }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="cancel" @click.prevent="$router.go(-1)">
 | 
			
		||||
            {{ t('buttons.CANCEL') }}
 | 
			
		||||
            {{ $t('buttons.CANCEL') }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="danger" @click.prevent="updateDisplayModal(true)">
 | 
			
		||||
            {{ t('buttons.DELETE_MY_ACCOUNT') }}
 | 
			
		||||
            {{ $t('buttons.DELETE_MY_ACCOUNT') }}
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
@@ -105,7 +105,6 @@
 | 
			
		||||
    ref,
 | 
			
		||||
    onMounted,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import CustomTextArea from '@/components/Common/CustomTextArea.vue'
 | 
			
		||||
  import ErrorMessage from '@/components/Common/ErrorMessage.vue'
 | 
			
		||||
@@ -128,7 +127,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const userForm: IUserPayload = reactive({
 | 
			
		||||
        password: '',
 | 
			
		||||
@@ -185,7 +183,6 @@
 | 
			
		||||
        errorMessages,
 | 
			
		||||
        loading,
 | 
			
		||||
        registrationDate,
 | 
			
		||||
        t,
 | 
			
		||||
        userForm,
 | 
			
		||||
        deleteAccount,
 | 
			
		||||
        updateBio,
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,16 @@
 | 
			
		||||
        />
 | 
			
		||||
        <div class="picture-buttons">
 | 
			
		||||
          <button type="submit" :disabled="!pictureFile">
 | 
			
		||||
            {{ t('user.PROFILE.PICTURE_UPDATE') }}
 | 
			
		||||
            {{ $t('user.PROFILE.PICTURE_UPDATE') }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="danger" v-if="user.picture" @click="deleteUserPicture">
 | 
			
		||||
            {{ t('user.PROFILE.PICTURE_REMOVE') }}
 | 
			
		||||
            {{ $t('user.PROFILE.PICTURE_REMOVE') }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="cancel" @click="$router.push('/profile')">
 | 
			
		||||
            {{ t('user.PROFILE.BACK_TO_PROFILE') }}
 | 
			
		||||
            {{ $t('user.PROFILE.BACK_TO_PROFILE') }}
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <span>{{ t('workouts.MAX_SIZE') }}: {{ fileSizeLimit }}</span>
 | 
			
		||||
        <span>{{ $t('workouts.MAX_SIZE') }}: {{ fileSizeLimit }}</span>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -36,7 +36,6 @@
 | 
			
		||||
    computed,
 | 
			
		||||
    ref,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import ErrorMessage from '@/components/Common/ErrorMessage.vue'
 | 
			
		||||
  import UserPicture from '@/components/User/UserPicture.vue'
 | 
			
		||||
@@ -59,7 +58,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const errorMessages: ComputedRef<string | string[] | null> = computed(
 | 
			
		||||
        () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
 | 
			
		||||
@@ -92,7 +90,6 @@
 | 
			
		||||
        errorMessages,
 | 
			
		||||
        fileSizeLimit,
 | 
			
		||||
        pictureFile,
 | 
			
		||||
        t,
 | 
			
		||||
        deleteUserPicture,
 | 
			
		||||
        updateUserPicture,
 | 
			
		||||
        updatePictureFile,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
      <ErrorMessage :message="errorMessages" v-if="errorMessages" />
 | 
			
		||||
      <form @submit.prevent="updateProfile">
 | 
			
		||||
        <label class="form-items">
 | 
			
		||||
          {{ t('user.PROFILE.LANGUAGE') }}
 | 
			
		||||
          {{ $t('user.PROFILE.LANGUAGE') }}
 | 
			
		||||
          <select id="language" v-model="userForm.language" :disabled="loading">
 | 
			
		||||
            <option
 | 
			
		||||
              v-for="lang in availableLanguages"
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
          </select>
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items" for="timezone">
 | 
			
		||||
          {{ t('user.PROFILE.TIMEZONE') }}
 | 
			
		||||
          {{ $t('user.PROFILE.TIMEZONE') }}
 | 
			
		||||
          <input
 | 
			
		||||
            id="timezone"
 | 
			
		||||
            v-model="userForm.timezone"
 | 
			
		||||
@@ -24,23 +24,23 @@
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="form-items">
 | 
			
		||||
          {{ t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
 | 
			
		||||
          {{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
 | 
			
		||||
          <select id="weekm" v-model="userForm.weekm" :disabled="loading">
 | 
			
		||||
            <option
 | 
			
		||||
              v-for="start in weekStart"
 | 
			
		||||
              :value="start.value"
 | 
			
		||||
              :key="start.value"
 | 
			
		||||
            >
 | 
			
		||||
              {{ t(`user.PROFILE.${start.label}`) }}
 | 
			
		||||
              {{ $t(`user.PROFILE.${start.label}`) }}
 | 
			
		||||
            </option>
 | 
			
		||||
          </select>
 | 
			
		||||
        </label>
 | 
			
		||||
        <div class="form-buttons">
 | 
			
		||||
          <button class="confirm" type="submit">
 | 
			
		||||
            {{ t('buttons.SUBMIT') }}
 | 
			
		||||
            {{ $t('buttons.SUBMIT') }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="cancel" @click.prevent="$router.go(-1)">
 | 
			
		||||
            {{ t('buttons.CANCEL') }}
 | 
			
		||||
            {{ $t('buttons.CANCEL') }}
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
@@ -76,7 +76,7 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t, availableLocales } = useI18n()
 | 
			
		||||
      const { availableLocales } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const userForm: IUserPreferencesPayload = reactive({
 | 
			
		||||
        language: '',
 | 
			
		||||
@@ -122,7 +122,6 @@
 | 
			
		||||
        availableLanguages,
 | 
			
		||||
        errorMessages,
 | 
			
		||||
        loading,
 | 
			
		||||
        t,
 | 
			
		||||
        userForm,
 | 
			
		||||
        weekStart,
 | 
			
		||||
        updateProfile,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="user-profile-edition">
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{ t(`user.PROFILE.${tab}_EDITION`) }}</template>
 | 
			
		||||
      <template #title>{{ $t(`user.PROFILE.${tab}_EDITION`) }}</template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        <UserProfileTabs
 | 
			
		||||
          :tabs="tabs"
 | 
			
		||||
@@ -18,8 +18,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, defineComponent, ref, computed } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
  import { PropType, computed, defineComponent, ref } from 'vue'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue'
 | 
			
		||||
@@ -50,14 +49,13 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES']
 | 
			
		||||
      const selectedTab = ref(props.tab)
 | 
			
		||||
      const loading = computed(
 | 
			
		||||
        () => store.getters[USER_STORE.GETTERS.USER_LOADING]
 | 
			
		||||
      )
 | 
			
		||||
      return { loading, selectedTab, t, tabs }
 | 
			
		||||
      return { loading, selectedTab, tabs }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="login-or-register-form">
 | 
			
		||||
  <div id="user-auth-form">
 | 
			
		||||
    <div id="user-form">
 | 
			
		||||
      <div
 | 
			
		||||
        class="form-box"
 | 
			
		||||
@@ -19,38 +19,57 @@
 | 
			
		||||
              :disabled="registration_disabled"
 | 
			
		||||
              required
 | 
			
		||||
              v-model="formData.username"
 | 
			
		||||
              :placeholder="t('user.USERNAME')"
 | 
			
		||||
              :placeholder="$t('user.USERNAME')"
 | 
			
		||||
            />
 | 
			
		||||
            <input
 | 
			
		||||
              v-if="action !== 'reset'"
 | 
			
		||||
              id="email"
 | 
			
		||||
              :disabled="registration_disabled"
 | 
			
		||||
              required
 | 
			
		||||
              type="email"
 | 
			
		||||
              v-model="formData.email"
 | 
			
		||||
              :placeholder="t('user.EMAIL')"
 | 
			
		||||
              :placeholder="
 | 
			
		||||
                action === 'reset-request'
 | 
			
		||||
                  ? $t('user.ENTER_EMAIL')
 | 
			
		||||
                  : $t('user.EMAIL')
 | 
			
		||||
              "
 | 
			
		||||
            />
 | 
			
		||||
            <input
 | 
			
		||||
              v-if="action !== 'reset-request'"
 | 
			
		||||
              id="password"
 | 
			
		||||
              :disabled="registration_disabled"
 | 
			
		||||
              required
 | 
			
		||||
              type="password"
 | 
			
		||||
              v-model="formData.password"
 | 
			
		||||
              :placeholder="t('user.PASSWORD')"
 | 
			
		||||
              :placeholder="
 | 
			
		||||
                action === 'reset'
 | 
			
		||||
                  ? $t('user.ENTER_PASSWORD')
 | 
			
		||||
                  : $t('user.PASSWORD')
 | 
			
		||||
              "
 | 
			
		||||
            />
 | 
			
		||||
            <input
 | 
			
		||||
              v-if="action === 'register'"
 | 
			
		||||
              v-if="['register', 'reset'].includes(action)"
 | 
			
		||||
              id="confirm-password"
 | 
			
		||||
              :disabled="registration_disabled"
 | 
			
		||||
              type="password"
 | 
			
		||||
              required
 | 
			
		||||
              v-model="formData.password_conf"
 | 
			
		||||
              :placeholder="t('user.PASSWORD_CONFIRM')"
 | 
			
		||||
              :placeholder="
 | 
			
		||||
                action === 'reset'
 | 
			
		||||
                  ? $t('user.ENTER_PASSWORD_CONFIRMATION')
 | 
			
		||||
                  : $t('user.PASSWORD_CONFIRM')
 | 
			
		||||
              "
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <button type="submit" :disabled="registration_disabled">
 | 
			
		||||
            {{ t(buttonText) }}
 | 
			
		||||
            {{ $t(buttonText) }}
 | 
			
		||||
          </button>
 | 
			
		||||
        </form>
 | 
			
		||||
        <div v-if="action === 'login'">
 | 
			
		||||
          <router-link class="password-forgotten" to="/password-reset/request">
 | 
			
		||||
            {{ $t('user.PASSWORD_FORGOTTEN') }}
 | 
			
		||||
          </router-link>
 | 
			
		||||
        </div>
 | 
			
		||||
        <ErrorMessage :message="errorMessages" v-if="errorMessages" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -59,19 +78,17 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { ComputedRef, computed, defineComponent, reactive, watch } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
  import { useRoute } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
  import AlertMessage from '@/components/Common/AlertMessage.vue'
 | 
			
		||||
  import ErrorMessage from '@/components/Common/ErrorMessage.vue'
 | 
			
		||||
  import router from '@/router'
 | 
			
		||||
  import { ROOT_STORE, USER_STORE } from '@/store/constants'
 | 
			
		||||
  import { IAppConfig } from '@/types/application'
 | 
			
		||||
  import { ILoginRegisterFormData } from '@/types/user'
 | 
			
		||||
  import { useStore } from '@/use/useStore'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'LoginOrRegisterForm',
 | 
			
		||||
    name: 'UserAuthForm',
 | 
			
		||||
    components: {
 | 
			
		||||
      AlertMessage,
 | 
			
		||||
      ErrorMessage,
 | 
			
		||||
@@ -81,6 +98,10 @@
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
      token: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const formData: ILoginRegisterFormData = reactive({
 | 
			
		||||
@@ -89,12 +110,11 @@
 | 
			
		||||
        password: '',
 | 
			
		||||
        password_conf: '',
 | 
			
		||||
      })
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const route = useRoute()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
 | 
			
		||||
      const buttonText: ComputedRef<string> = computed(() =>
 | 
			
		||||
        props.action === 'register' ? 'buttons.REGISTER' : 'buttons.LOGIN'
 | 
			
		||||
        getButtonText(props.action)
 | 
			
		||||
      )
 | 
			
		||||
      const errorMessages: ComputedRef<string | string[] | null> = computed(
 | 
			
		||||
        () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
 | 
			
		||||
@@ -108,11 +128,42 @@
 | 
			
		||||
          !appConfig.value.is_registration_enabled
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      function getButtonText(action: string): string {
 | 
			
		||||
        switch (action) {
 | 
			
		||||
          case 'reset-request':
 | 
			
		||||
          case 'reset':
 | 
			
		||||
            return 'buttons.SUBMIT'
 | 
			
		||||
          default:
 | 
			
		||||
            return `buttons.${props.action.toUpperCase()}`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      function onSubmit(actionType: string) {
 | 
			
		||||
        return store.dispatch(USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
 | 
			
		||||
          actionType,
 | 
			
		||||
          formData,
 | 
			
		||||
        })
 | 
			
		||||
        switch (actionType) {
 | 
			
		||||
          case 'reset':
 | 
			
		||||
            if (!props.token) {
 | 
			
		||||
              return store.commit(
 | 
			
		||||
                ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
 | 
			
		||||
                'user.INVALID_TOKEN'
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
            return store.dispatch(USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
 | 
			
		||||
              password: formData.password,
 | 
			
		||||
              password_conf: formData.password_conf,
 | 
			
		||||
              token: props.token,
 | 
			
		||||
            })
 | 
			
		||||
          case 'reset-request':
 | 
			
		||||
            return store.dispatch(
 | 
			
		||||
              USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
 | 
			
		||||
              {
 | 
			
		||||
                email: formData.email,
 | 
			
		||||
              }
 | 
			
		||||
            )
 | 
			
		||||
          default:
 | 
			
		||||
            store.dispatch(USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
 | 
			
		||||
              actionType,
 | 
			
		||||
              formData,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      function resetFormData() {
 | 
			
		||||
        formData.username = ''
 | 
			
		||||
@@ -128,13 +179,11 @@
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
      return {
 | 
			
		||||
        t,
 | 
			
		||||
        appConfig,
 | 
			
		||||
        buttonText,
 | 
			
		||||
        errorMessages,
 | 
			
		||||
        formData,
 | 
			
		||||
        registration_disabled,
 | 
			
		||||
        router,
 | 
			
		||||
        onSubmit,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -144,7 +193,7 @@
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
  @import '~@/scss/base';
 | 
			
		||||
 | 
			
		||||
  #login-or-register-form {
 | 
			
		||||
  #user-auth-form {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 | 
			
		||||
@@ -154,6 +203,12 @@
 | 
			
		||||
    #user-form {
 | 
			
		||||
      width: 60%;
 | 
			
		||||
 | 
			
		||||
      .password-forgotten {
 | 
			
		||||
        font-size: 0.9em;
 | 
			
		||||
        font-style: italic;
 | 
			
		||||
        padding-left: $default-padding;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      button {
 | 
			
		||||
        margin: $default-margin;
 | 
			
		||||
        border: solid 1px var(--app-color);
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    <img
 | 
			
		||||
      v-if="authUserPictureUrl !== ''"
 | 
			
		||||
      class="nav-profile-user-img"
 | 
			
		||||
      :alt="t('user.USER_PICTURE')"
 | 
			
		||||
      :alt="$t('user.USER_PICTURE')"
 | 
			
		||||
      :src="authUserPictureUrl"
 | 
			
		||||
    />
 | 
			
		||||
    <div v-else class="no-picture">
 | 
			
		||||
@@ -14,10 +14,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import { IAuthUserProfile } from '@/types/user'
 | 
			
		||||
  import { getApiUrl } from '@/utils'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'UserPicture',
 | 
			
		||||
    props: {
 | 
			
		||||
@@ -27,14 +27,12 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return {
 | 
			
		||||
        authUserPictureUrl: computed(() =>
 | 
			
		||||
          props.user.picture
 | 
			
		||||
            ? `${getApiUrl()}users/${props.user.username}/picture?${Date.now()}`
 | 
			
		||||
            : ''
 | 
			
		||||
        ),
 | 
			
		||||
        t,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
            :disabled="disabled"
 | 
			
		||||
            @input="$router.push(getPath(tab))"
 | 
			
		||||
          />
 | 
			
		||||
          <span>{{ t(`user.PROFILE.TABS.${tab}`) }}</span>
 | 
			
		||||
          <span>{{ $t(`user.PROFILE.TABS.${tab}`) }}</span>
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -20,7 +20,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'UserProfileTabs',
 | 
			
		||||
@@ -43,7 +42,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      function getPath(tab: string) {
 | 
			
		||||
        switch (tab) {
 | 
			
		||||
          case 'PICTURE':
 | 
			
		||||
@@ -55,7 +53,7 @@
 | 
			
		||||
            return `/profile${props.edition ? '/edit' : ''}`
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return { t, getPath }
 | 
			
		||||
      return { getPath }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
          <img
 | 
			
		||||
            class="profile-img"
 | 
			
		||||
            v-if="userPictureUrl !== ''"
 | 
			
		||||
            :alt="t('user.USER_PICTURE')"
 | 
			
		||||
            :alt="$t('user.USER_PICTURE')"
 | 
			
		||||
            :src="userPictureUrl"
 | 
			
		||||
          />
 | 
			
		||||
          <div v-else class="no-picture">
 | 
			
		||||
@@ -49,7 +49,7 @@
 | 
			
		||||
        <div v-if="workout">
 | 
			
		||||
          <StaticMap v-if="workout.with_gpx" :workout="workout" />
 | 
			
		||||
          <div v-else class="no-map">
 | 
			
		||||
            {{ t('workouts.NO_MAP') }}
 | 
			
		||||
            {{ $t('workouts.NO_MAP') }}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -78,9 +78,8 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { Locale, format, formatDistance } from 'date-fns'
 | 
			
		||||
  import { PropType, defineComponent, ComputedRef, computed } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import StaticMap from '@/components/Common/StaticMap.vue'
 | 
			
		||||
  import { ROOT_STORE } from '@/store/constants'
 | 
			
		||||
  import { ISport } from '@/types/sports'
 | 
			
		||||
@@ -111,7 +110,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
 | 
			
		||||
      const userPictureUrl: ComputedRef<string> = computed(() =>
 | 
			
		||||
@@ -128,7 +126,6 @@
 | 
			
		||||
        formatDistance,
 | 
			
		||||
        getDateWithTZ,
 | 
			
		||||
        locale,
 | 
			
		||||
        t,
 | 
			
		||||
        userPictureUrl,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="workout-chart">
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{ t('workouts.ANALYSIS') }} </template>
 | 
			
		||||
      <template #title>{{ $t('workouts.ANALYSIS') }} </template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        <div class="chart-radio">
 | 
			
		||||
          <label>
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
              :checked="displayDistance"
 | 
			
		||||
              @click="updateDisplayDistance"
 | 
			
		||||
            />
 | 
			
		||||
            {{ t('workouts.DISTANCE') }}
 | 
			
		||||
            {{ $t('workouts.DISTANCE') }}
 | 
			
		||||
          </label>
 | 
			
		||||
          <label>
 | 
			
		||||
            <input
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
              :checked="!displayDistance"
 | 
			
		||||
              @click="updateDisplayDistance"
 | 
			
		||||
            />
 | 
			
		||||
            {{ t('workouts.DURATION') }}
 | 
			
		||||
            {{ $t('workouts.DURATION') }}
 | 
			
		||||
          </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <LineChart
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
          @mouseleave="emitEmptyCoordinates"
 | 
			
		||||
        />
 | 
			
		||||
        <div class="no-data-cleaning">
 | 
			
		||||
          {{ t('workouts.NO_DATA_CLEANING') }}
 | 
			
		||||
          {{ $t('workouts.NO_DATA_CLEANING') }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </Card>
 | 
			
		||||
@@ -201,7 +201,6 @@
 | 
			
		||||
      return {
 | 
			
		||||
        displayDistance,
 | 
			
		||||
        lineChartProps,
 | 
			
		||||
        t,
 | 
			
		||||
        emitEmptyCoordinates,
 | 
			
		||||
        updateDisplayDistance,
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
      :class="{ inactive: !workoutObject.previousUrl }"
 | 
			
		||||
      :title="
 | 
			
		||||
        workoutObject.previousUrl
 | 
			
		||||
          ? t(`workouts.PREVIOUS_${workoutObject.type}`)
 | 
			
		||||
          : t(`workouts.NO_PREVIOUS_${workoutObject.type}`)
 | 
			
		||||
          ? $t(`workouts.PREVIOUS_${workoutObject.type}`)
 | 
			
		||||
          : $t(`workouts.NO_PREVIOUS_${workoutObject.type}`)
 | 
			
		||||
      "
 | 
			
		||||
      @click="
 | 
			
		||||
        workoutObject.previousUrl
 | 
			
		||||
@@ -42,7 +42,7 @@
 | 
			
		||||
          <span class="workout-segment">
 | 
			
		||||
            —
 | 
			
		||||
            <i class="fa fa-map-marker" aria-hidden="true" />
 | 
			
		||||
            {{ t('workouts.SEGMENT') }}
 | 
			
		||||
            {{ $t('workouts.SEGMENT') }}
 | 
			
		||||
            {{ workoutObject.segmentId + 1 }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -57,7 +57,7 @@
 | 
			
		||||
                params: { workoutId: workoutObject.workoutId },
 | 
			
		||||
              }"
 | 
			
		||||
            >
 | 
			
		||||
              > {{ t('workouts.BACK_TO_WORKOUT') }}
 | 
			
		||||
              > {{ $t('workouts.BACK_TO_WORKOUT') }}
 | 
			
		||||
            </router-link></span
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -68,8 +68,8 @@
 | 
			
		||||
      :class="{ inactive: !workoutObject.nextUrl }"
 | 
			
		||||
      :title="
 | 
			
		||||
        workoutObject.nextUrl
 | 
			
		||||
          ? t(`workouts.NEXT_${workoutObject.type}`)
 | 
			
		||||
          : t(`workouts.NO_NEXT_${workoutObject.type}`)
 | 
			
		||||
          ? $t(`workouts.NEXT_${workoutObject.type}`)
 | 
			
		||||
          : $t(`workouts.NO_NEXT_${workoutObject.type}`)
 | 
			
		||||
      "
 | 
			
		||||
      @click="
 | 
			
		||||
        workoutObject.nextUrl ? $router.push(workoutObject.nextUrl) : null
 | 
			
		||||
@@ -82,9 +82,8 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import { ISport } from '@/types/sports'
 | 
			
		||||
  import { IWorkoutObject } from '@/types/workouts'
 | 
			
		||||
 | 
			
		||||
@@ -105,8 +104,7 @@
 | 
			
		||||
    },
 | 
			
		||||
    emits: ['displayModal'],
 | 
			
		||||
    setup(props, { emit }) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t, emit }
 | 
			
		||||
      return { emit }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,25 +2,26 @@
 | 
			
		||||
  <div id="workout-info">
 | 
			
		||||
    <div class="workout-data">
 | 
			
		||||
      <i class="fa fa-clock-o" aria-hidden="true" />
 | 
			
		||||
      {{ t('workouts.DURATION') }}: <span>{{ workoutObject.moving }}</span>
 | 
			
		||||
      {{ $t('workouts.DURATION') }}: <span>{{ workoutObject.moving }}</span>
 | 
			
		||||
      <WorkoutRecord :workoutObject="workoutObject" record_type="LD" />
 | 
			
		||||
      <div v-if="withPause">
 | 
			
		||||
        ({{ t('workouts.PAUSES') }}: <span>{{ workoutObject.pauses }}</span> -
 | 
			
		||||
        {{ t('workouts.TOTAL_DURATION') }}:
 | 
			
		||||
        ({{ $t('workouts.PAUSES') }}: <span>{{ workoutObject.pauses }}</span> -
 | 
			
		||||
        {{ $t('workouts.TOTAL_DURATION') }}:
 | 
			
		||||
        <span>{{ workoutObject.duration }})</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="workout-data">
 | 
			
		||||
      <i class="fa fa-road" aria-hidden="true" />
 | 
			
		||||
      {{ t('workouts.DISTANCE') }}: <span>{{ workoutObject.distance }} km</span>
 | 
			
		||||
      {{ $t('workouts.DISTANCE') }}:
 | 
			
		||||
      <span>{{ workoutObject.distance }} km</span>
 | 
			
		||||
      <WorkoutRecord :workoutObject="workoutObject" record_type="FD" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="workout-data">
 | 
			
		||||
      <i class="fa fa-tachometer" aria-hidden="true" />
 | 
			
		||||
      {{ t('workouts.AVERAGE_SPEED') }}:
 | 
			
		||||
      {{ $t('workouts.AVERAGE_SPEED') }}:
 | 
			
		||||
      <span>{{ workoutObject.aveSpeed }} km/h</span
 | 
			
		||||
      ><WorkoutRecord :workoutObject="workoutObject" record_type="AS" /><br />
 | 
			
		||||
      {{ t('workouts.MAX_SPEED') }}:
 | 
			
		||||
      {{ $t('workouts.MAX_SPEED') }}:
 | 
			
		||||
      <span>{{ workoutObject.maxSpeed }} km/h</span>
 | 
			
		||||
      <WorkoutRecord :workoutObject="workoutObject" record_type="MS" />
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -31,11 +32,11 @@
 | 
			
		||||
      <img
 | 
			
		||||
        class="mountains"
 | 
			
		||||
        src="/img/workouts/mountains.svg"
 | 
			
		||||
        :alt="t('workouts.ELEVATION')"
 | 
			
		||||
        :alt="$t('workouts.ELEVATION')"
 | 
			
		||||
      />
 | 
			
		||||
      {{ t('workouts.MIN_ALTITUDE') }}: <span>{{ workoutObject.minAlt }} m</span
 | 
			
		||||
      ><br />
 | 
			
		||||
      {{ t('workouts.MAX_ALTITUDE') }}:
 | 
			
		||||
      {{ $t('workouts.MIN_ALTITUDE') }}:
 | 
			
		||||
      <span>{{ workoutObject.minAlt }} m</span><br />
 | 
			
		||||
      {{ $t('workouts.MAX_ALTITUDE') }}:
 | 
			
		||||
      <span>{{ workoutObject.maxAlt }} m</span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
@@ -43,9 +44,9 @@
 | 
			
		||||
      v-if="workoutObject.ascent !== null && workoutObject.descent !== null"
 | 
			
		||||
    >
 | 
			
		||||
      <i class="fa fa-location-arrow" aria-hidden="true" />
 | 
			
		||||
      {{ t('workouts.ASCENT') }}: <span>{{ workoutObject.ascent }} m</span
 | 
			
		||||
      {{ $t('workouts.ASCENT') }}: <span>{{ workoutObject.ascent }} m</span
 | 
			
		||||
      ><br />
 | 
			
		||||
      {{ t('workouts.DESCENT') }}: <span>{{ workoutObject.descent }} m</span>
 | 
			
		||||
      {{ $t('workouts.DESCENT') }}: <span>{{ workoutObject.descent }} m</span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <WorkoutWeather :workoutObject="workoutObject" />
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -53,7 +54,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, computed, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import WorkoutRecord from '@/components/Workout/WorkoutDetail/WorkoutRecord.vue'
 | 
			
		||||
  import WorkoutWeather from '@/components/Workout/WorkoutDetail/WorkoutWeather.vue'
 | 
			
		||||
@@ -72,14 +72,12 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return {
 | 
			
		||||
        withPause: computed(
 | 
			
		||||
          () =>
 | 
			
		||||
            props.workoutObject.pauses !== '0:00:00' &&
 | 
			
		||||
            props.workoutObject.pauses !== null
 | 
			
		||||
        ),
 | 
			
		||||
        t,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
          />
 | 
			
		||||
        </LMap>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-else class="no-map">{{ t('workouts.NO_MAP') }}</div>
 | 
			
		||||
      <div v-else class="no-map">{{ $t('workouts.NO_MAP') }}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -32,7 +32,6 @@
 | 
			
		||||
  import { gpx } from '@tmcw/togeojson'
 | 
			
		||||
  import { LGeoJson, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
 | 
			
		||||
  import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import { ROOT_STORE } from '@/store/constants'
 | 
			
		||||
  import { IAppConfig } from '@/types/application'
 | 
			
		||||
@@ -59,7 +58,6 @@
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
 | 
			
		||||
      function getGeoJson(gpxContent: string): GeoJSONData {
 | 
			
		||||
@@ -120,7 +118,6 @@
 | 
			
		||||
        bounds,
 | 
			
		||||
        center,
 | 
			
		||||
        geoJson,
 | 
			
		||||
        t,
 | 
			
		||||
        workoutMap,
 | 
			
		||||
        fitBounds,
 | 
			
		||||
        getApiUrl,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,17 +9,17 @@
 | 
			
		||||
          <th />
 | 
			
		||||
          <th>
 | 
			
		||||
            <div class="weather-th">
 | 
			
		||||
              {{ t('workouts.START') }}
 | 
			
		||||
              {{ $t('workouts.START') }}
 | 
			
		||||
              <img
 | 
			
		||||
                class="weather-img"
 | 
			
		||||
                :src="`/img/weather/${workoutObject.weatherStart.icon}.svg`"
 | 
			
		||||
                :alt="
 | 
			
		||||
                  t(
 | 
			
		||||
                  $t(
 | 
			
		||||
                    `workouts.WEATHER.DARK_SKY.${workoutObject.weatherStart.icon}`
 | 
			
		||||
                  )
 | 
			
		||||
                "
 | 
			
		||||
                :title="
 | 
			
		||||
                  t(
 | 
			
		||||
                  $t(
 | 
			
		||||
                    `workouts.WEATHER.DARK_SKY.${workoutObject.weatherStart.icon}`
 | 
			
		||||
                  )
 | 
			
		||||
                "
 | 
			
		||||
@@ -28,17 +28,17 @@
 | 
			
		||||
          </th>
 | 
			
		||||
          <th>
 | 
			
		||||
            <div class="weather-th">
 | 
			
		||||
              {{ t('workouts.END') }}
 | 
			
		||||
              {{ $t('workouts.END') }}
 | 
			
		||||
              <img
 | 
			
		||||
                class="weather-img"
 | 
			
		||||
                :src="`/img/weather/${workoutObject.weatherEnd.icon}.svg`"
 | 
			
		||||
                :alt="
 | 
			
		||||
                  t(
 | 
			
		||||
                  $t(
 | 
			
		||||
                    `workouts.WEATHER.DARK_SKY.${workoutObject.weatherEnd.icon}`
 | 
			
		||||
                  )
 | 
			
		||||
                "
 | 
			
		||||
                :title="
 | 
			
		||||
                  t(
 | 
			
		||||
                  $t(
 | 
			
		||||
                    `workouts.WEATHER.DARK_SKY.${workoutObject.weatherEnd.icon}`
 | 
			
		||||
                  )
 | 
			
		||||
                "
 | 
			
		||||
@@ -53,8 +53,8 @@
 | 
			
		||||
            <img
 | 
			
		||||
              class="weather-img weather-img-small"
 | 
			
		||||
              src="/img/weather/temperature.svg"
 | 
			
		||||
              :alt="t(`workouts.WEATHER.TEMPERATURE`)"
 | 
			
		||||
              :title="t(`workouts.WEATHER.TEMPERATURE`)"
 | 
			
		||||
              :alt="$t(`workouts.WEATHER.TEMPERATURE`)"
 | 
			
		||||
              :title="$t(`workouts.WEATHER.TEMPERATURE`)"
 | 
			
		||||
            />
 | 
			
		||||
          </td>
 | 
			
		||||
          <td>
 | 
			
		||||
@@ -69,8 +69,8 @@
 | 
			
		||||
            <img
 | 
			
		||||
              class="weather-img weather-img-small"
 | 
			
		||||
              src="/img/weather/pour-rain.svg"
 | 
			
		||||
              :alt="t(`workouts.WEATHER.HUMIDITY`)"
 | 
			
		||||
              :title="t(`workouts.WEATHER.HUMIDITY`)"
 | 
			
		||||
              :alt="$t(`workouts.WEATHER.HUMIDITY`)"
 | 
			
		||||
              :title="$t(`workouts.WEATHER.HUMIDITY`)"
 | 
			
		||||
            />
 | 
			
		||||
          </td>
 | 
			
		||||
          <td>
 | 
			
		||||
@@ -85,8 +85,8 @@
 | 
			
		||||
            <img
 | 
			
		||||
              class="weather-img weather-img-small"
 | 
			
		||||
              src="/img/weather/breeze.svg"
 | 
			
		||||
              :alt="t(`workouts.WEATHER.WIND`)"
 | 
			
		||||
              :title="t(`workouts.WEATHER.WIND`)"
 | 
			
		||||
              :alt="$t(`workouts.WEATHER.WIND`)"
 | 
			
		||||
              :title="$t(`workouts.WEATHER.WIND`)"
 | 
			
		||||
            />
 | 
			
		||||
          </td>
 | 
			
		||||
          <td>{{ Number(workoutObject.weatherStart.wind).toFixed(1) }}m/s</td>
 | 
			
		||||
@@ -99,9 +99,9 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent, PropType } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import { IWorkoutObject } from '@/types/workouts'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'WorkoutWeather',
 | 
			
		||||
    props: {
 | 
			
		||||
@@ -110,10 +110,6 @@
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
  <div class="workout-detail">
 | 
			
		||||
    <Modal
 | 
			
		||||
      v-if="displayModal"
 | 
			
		||||
      :title="t('common.CONFIRMATION')"
 | 
			
		||||
      :message="t('workouts.WORKOUT_DELETION_CONFIRMATION')"
 | 
			
		||||
      :title="$t('common.CONFIRMATION')"
 | 
			
		||||
      :message="$t('workouts.WORKOUT_DELETION_CONFIRMATION')"
 | 
			
		||||
      @confirmAction="deleteWorkout(workoutObject.workoutId)"
 | 
			
		||||
      @cancelAction="updateDisplayModal(false)"
 | 
			
		||||
    />
 | 
			
		||||
@@ -36,7 +36,6 @@
 | 
			
		||||
    ref,
 | 
			
		||||
    watch,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
  import { useRoute } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
@@ -90,7 +89,6 @@
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const route = useRoute()
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
 | 
			
		||||
      function getWorkoutObjectUrl(
 | 
			
		||||
        workout: IWorkout,
 | 
			
		||||
@@ -196,7 +194,6 @@
 | 
			
		||||
          getWorkoutObject(workout.value, segment.value)
 | 
			
		||||
        ),
 | 
			
		||||
        displayModal,
 | 
			
		||||
        t,
 | 
			
		||||
        deleteWorkout,
 | 
			
		||||
        updateDisplayModal,
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
  >
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{
 | 
			
		||||
        t(`workouts.${isCreation ? 'ADD' : 'EDIT'}_WORKOUT`)
 | 
			
		||||
        $t(`workouts.${isCreation ? 'ADD' : 'EDIT'}_WORKOUT`)
 | 
			
		||||
      }}</template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        <div id="workout-form">
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
                    :disabled="loading"
 | 
			
		||||
                    @click="updateWithGpx"
 | 
			
		||||
                  />
 | 
			
		||||
                  <label for="withGpx">{{ t('workouts.WITH_GPX') }}</label>
 | 
			
		||||
                  <label for="withGpx">{{ $t('workouts.WITH_GPX') }}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <input
 | 
			
		||||
@@ -31,12 +31,12 @@
 | 
			
		||||
                    @click="updateWithGpx"
 | 
			
		||||
                  />
 | 
			
		||||
                  <label for="withoutGpx">{{
 | 
			
		||||
                    t('workouts.WITHOUT_GPX')
 | 
			
		||||
                    $t('workouts.WITHOUT_GPX')
 | 
			
		||||
                  }}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="form-item">
 | 
			
		||||
                <label> {{ t('workouts.SPORT', 1) }}: </label>
 | 
			
		||||
                <label> {{ $t('workouts.SPORT', 1) }}: </label>
 | 
			
		||||
                <select
 | 
			
		||||
                  id="sport"
 | 
			
		||||
                  required
 | 
			
		||||
@@ -54,8 +54,8 @@
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="form-item" v-if="isCreation && withGpx">
 | 
			
		||||
                <label for="gpxFile">
 | 
			
		||||
                  {{ t('workouts.GPX_FILE') }}
 | 
			
		||||
                  {{ t('workouts.ZIP_ARCHIVE_DESCRIPTION') }}:
 | 
			
		||||
                  {{ $t('workouts.GPX_FILE') }}
 | 
			
		||||
                  {{ $t('workouts.ZIP_ARCHIVE_DESCRIPTION') }}:
 | 
			
		||||
                </label>
 | 
			
		||||
                <input
 | 
			
		||||
                  id="gpxFile"
 | 
			
		||||
@@ -67,26 +67,28 @@
 | 
			
		||||
                />
 | 
			
		||||
                <div class="files-help">
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong>{{ t('workouts.GPX_FILE') }}:</strong>
 | 
			
		||||
                    <strong>{{ $t('workouts.GPX_FILE') }}:</strong>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                      <li>{{ t('workouts.MAX_SIZE') }}: {{ fileSizeLimit }}</li>
 | 
			
		||||
                      <li>
 | 
			
		||||
                        {{ $t('workouts.MAX_SIZE') }}: {{ fileSizeLimit }}
 | 
			
		||||
                      </li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <strong>{{ t('workouts.ZIP_ARCHIVE') }}:</strong>
 | 
			
		||||
                    <strong>{{ $t('workouts.ZIP_ARCHIVE') }}:</strong>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                      <li>{{ t('workouts.NO_FOLDER') }}</li>
 | 
			
		||||
                      <li>{{ $t('workouts.NO_FOLDER') }}</li>
 | 
			
		||||
                      <li>
 | 
			
		||||
                        {{ t('workouts.MAX_FILES') }}: {{ gpx_limit_import }}
 | 
			
		||||
                        {{ $t('workouts.MAX_FILES') }}: {{ gpx_limit_import }}
 | 
			
		||||
                      </li>
 | 
			
		||||
                      <li>{{ t('workouts.MAX_SIZE') }}: {{ zipSizeLimit }}</li>
 | 
			
		||||
                      <li>{{ $t('workouts.MAX_SIZE') }}: {{ zipSizeLimit }}</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="form-item" v-else>
 | 
			
		||||
                <label for="title"> {{ t('workouts.TITLE') }}: </label>
 | 
			
		||||
                <label for="title"> {{ $t('workouts.TITLE') }}: </label>
 | 
			
		||||
                <input
 | 
			
		||||
                  id="title"
 | 
			
		||||
                  name="title"
 | 
			
		||||
@@ -99,7 +101,7 @@
 | 
			
		||||
              <div v-if="!withGpx">
 | 
			
		||||
                <div class="workout-date-duration">
 | 
			
		||||
                  <div class="form-item">
 | 
			
		||||
                    <label>{{ t('workouts.WORKOUT_DATE') }}:</label>
 | 
			
		||||
                    <label>{{ $t('workouts.WORKOUT_DATE') }}:</label>
 | 
			
		||||
                    <div class="workout-date-time">
 | 
			
		||||
                      <input
 | 
			
		||||
                        id="workout-date"
 | 
			
		||||
@@ -121,7 +123,7 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="form-item">
 | 
			
		||||
                    <label>{{ t('workouts.DURATION') }}:</label>
 | 
			
		||||
                    <label>{{ $t('workouts.DURATION') }}:</label>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <input
 | 
			
		||||
                        id="workout-duration-hour"
 | 
			
		||||
@@ -162,7 +164,7 @@
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-item">
 | 
			
		||||
                  <label>{{ t('workouts.DISTANCE') }} (km):</label>
 | 
			
		||||
                  <label>{{ $t('workouts.DISTANCE') }} (km):</label>
 | 
			
		||||
                  <input
 | 
			
		||||
                    type="number"
 | 
			
		||||
                    min="0"
 | 
			
		||||
@@ -174,7 +176,7 @@
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="form-item">
 | 
			
		||||
                <label> {{ t('workouts.NOTES') }}: </label>
 | 
			
		||||
                <label> {{ $t('workouts.NOTES') }}: </label>
 | 
			
		||||
                <CustomTextArea
 | 
			
		||||
                  name="notes"
 | 
			
		||||
                  :input="workoutDataObject.notes"
 | 
			
		||||
@@ -189,10 +191,10 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else class="form-buttons">
 | 
			
		||||
              <button class="confirm" type="submit" :disabled="loading">
 | 
			
		||||
                {{ t('buttons.SUBMIT') }}
 | 
			
		||||
                {{ $t('buttons.SUBMIT') }}
 | 
			
		||||
              </button>
 | 
			
		||||
              <button class="cancel" @click.prevent="onCancel">
 | 
			
		||||
                {{ t('buttons.CANCEL') }}
 | 
			
		||||
                {{ $t('buttons.CANCEL') }}
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
@@ -406,7 +408,6 @@
 | 
			
		||||
        errorMessages,
 | 
			
		||||
        fileSizeLimit,
 | 
			
		||||
        gpx_limit_import,
 | 
			
		||||
        t,
 | 
			
		||||
        translatedSports,
 | 
			
		||||
        withGpx,
 | 
			
		||||
        zipSizeLimit,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="workout-note">
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{ t('workouts.NOTES') }}</template>
 | 
			
		||||
      <template #title>{{ $t('workouts.NOTES') }}</template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        {{ notes && notes !== '' ? notes : t('workouts.NO_NOTES') }}</template
 | 
			
		||||
        {{ notes && notes !== '' ? notes : $t('workouts.NO_NOTES') }}</template
 | 
			
		||||
      >
 | 
			
		||||
    </Card>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
 | 
			
		||||
@@ -26,10 +25,6 @@
 | 
			
		||||
        required: false,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="workout-segments">
 | 
			
		||||
    <Card>
 | 
			
		||||
      <template #title>{{ t('workouts.SEGMENT', 2) }}</template>
 | 
			
		||||
      <template #title>{{ $t('workouts.SEGMENT', 2) }}</template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
        <ul>
 | 
			
		||||
          <li v-for="(segment, index) in segments" :key="segment.segment_id">
 | 
			
		||||
@@ -13,10 +13,10 @@
 | 
			
		||||
                  segmentId: index + 1,
 | 
			
		||||
                },
 | 
			
		||||
              }"
 | 
			
		||||
              >{{ t('workouts.SEGMENT', 1) }} {{ index + 1 }}</router-link
 | 
			
		||||
              >{{ $t('workouts.SEGMENT', 1) }} {{ index + 1 }}</router-link
 | 
			
		||||
            >
 | 
			
		||||
            ({{ t('workouts.DISTANCE') }}: {{ segment.distance }} km,
 | 
			
		||||
            {{ t('workouts.DURATION') }}: {{ segment.duration }})
 | 
			
		||||
            ({{ $t('workouts.DISTANCE') }}: {{ segment.distance }} km,
 | 
			
		||||
            {{ $t('workouts.DURATION') }}: {{ segment.duration }})
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </template>
 | 
			
		||||
@@ -26,7 +26,6 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { PropType, defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import Card from '@/components/Common/Card.vue'
 | 
			
		||||
  import { IWorkoutSegment } from '@/types/workouts'
 | 
			
		||||
@@ -42,10 +41,6 @@
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="no-workouts box">
 | 
			
		||||
    <div>
 | 
			
		||||
      {{ t('workouts.NO_WORKOUTS') }}
 | 
			
		||||
      {{ $t('workouts.NO_WORKOUTS') }}
 | 
			
		||||
      <router-link to="/workouts/add">
 | 
			
		||||
        {{ t('workouts.UPLOAD_FIRST_WORKOUT') }}
 | 
			
		||||
        {{ $t('workouts.UPLOAD_FIRST_WORKOUT') }}
 | 
			
		||||
      </router-link>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -11,14 +11,9 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'NoWorkouts',
 | 
			
		||||
    setup() {
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      return { t }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,18 +4,18 @@
 | 
			
		||||
      <div class="form">
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.FROM') }}: </label>
 | 
			
		||||
            <label> {{ $t('workouts.FROM') }}: </label>
 | 
			
		||||
            <input name="from" type="date" @change="handleFilterChange" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.TO') }}: </label>
 | 
			
		||||
            <label> {{ $t('workouts.TO') }}: </label>
 | 
			
		||||
            <input name="to" type="date" @change="handleFilterChange" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.SPORT', 1) }}:</label>
 | 
			
		||||
            <label> {{ $t('workouts.SPORT', 1) }}:</label>
 | 
			
		||||
            <select name="sport_id" @change="handleFilterChange">
 | 
			
		||||
              <option value="" />
 | 
			
		||||
              <option
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.DISTANCE') }} (km): </label>
 | 
			
		||||
            <label> {{ $t('workouts.DISTANCE') }} (km): </label>
 | 
			
		||||
            <div class="form-inputs-group">
 | 
			
		||||
              <input
 | 
			
		||||
                name="distance_from"
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
                step="1"
 | 
			
		||||
                @change="handleFilterChange"
 | 
			
		||||
              />
 | 
			
		||||
              <span>{{ t('workouts.TO') }}</span>
 | 
			
		||||
              <span>{{ $t('workouts.TO') }}</span>
 | 
			
		||||
              <input
 | 
			
		||||
                name="distance_to"
 | 
			
		||||
                type="number"
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.DURATION') }} (km): </label>
 | 
			
		||||
            <label> {{ $t('workouts.DURATION') }} (km): </label>
 | 
			
		||||
            <div class="form-inputs-group">
 | 
			
		||||
              <input
 | 
			
		||||
                name="duration_from"
 | 
			
		||||
@@ -63,7 +63,7 @@
 | 
			
		||||
                placeholder="hh:mm"
 | 
			
		||||
                type="text"
 | 
			
		||||
              />
 | 
			
		||||
              <span>{{ t('workouts.TO') }}</span>
 | 
			
		||||
              <span>{{ $t('workouts.TO') }}</span>
 | 
			
		||||
              <input
 | 
			
		||||
                name="duration_to"
 | 
			
		||||
                @change="handleFilterChange"
 | 
			
		||||
@@ -77,7 +77,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.AVE_SPEED') }} (km): </label>
 | 
			
		||||
            <label> {{ $t('workouts.AVE_SPEED') }} (km): </label>
 | 
			
		||||
            <div class="form-inputs-group">
 | 
			
		||||
              <input
 | 
			
		||||
                min="0"
 | 
			
		||||
@@ -86,7 +86,7 @@
 | 
			
		||||
                step="1"
 | 
			
		||||
                type="number"
 | 
			
		||||
              />
 | 
			
		||||
              <span>{{ t('workouts.TO') }}</span>
 | 
			
		||||
              <span>{{ $t('workouts.TO') }}</span>
 | 
			
		||||
              <input
 | 
			
		||||
                min="0"
 | 
			
		||||
                name="ave_speed_to"
 | 
			
		||||
@@ -100,7 +100,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="form-items-group">
 | 
			
		||||
          <div class="form-item">
 | 
			
		||||
            <label> {{ t('workouts.MAX_SPEED') }} (km): </label>
 | 
			
		||||
            <label> {{ $t('workouts.MAX_SPEED') }} (km): </label>
 | 
			
		||||
 | 
			
		||||
            <div class="form-inputs-group">
 | 
			
		||||
              <input
 | 
			
		||||
@@ -110,7 +110,7 @@
 | 
			
		||||
                step="1"
 | 
			
		||||
                type="number"
 | 
			
		||||
              />
 | 
			
		||||
              <span>{{ t('workouts.TO') }}</span>
 | 
			
		||||
              <span>{{ $t('workouts.TO') }}</span>
 | 
			
		||||
              <input
 | 
			
		||||
                min="0"
 | 
			
		||||
                name="max_speed_to"
 | 
			
		||||
@@ -125,7 +125,7 @@
 | 
			
		||||
 | 
			
		||||
      <div class="form-button">
 | 
			
		||||
        <button class="confirm" @click="onFilter">
 | 
			
		||||
          {{ t('buttons.FILTER') }}
 | 
			
		||||
          {{ $t('buttons.FILTER') }}
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -166,7 +166,7 @@
 | 
			
		||||
        emit('filter', { ...params })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return { t, translatedSports, onFilter, handleFilterChange }
 | 
			
		||||
      return { translatedSports, onFilter, handleFilterChange }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,19 +6,19 @@
 | 
			
		||||
          <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <th class="sport-col" />
 | 
			
		||||
              <th>{{ capitalize(t('workouts.WORKOUT', 1)) }}</th>
 | 
			
		||||
              <th>{{ capitalize(t('workouts.DATE')) }}</th>
 | 
			
		||||
              <th>{{ capitalize(t('workouts.DISTANCE')) }}</th>
 | 
			
		||||
              <th>{{ capitalize(t('workouts.DURATION')) }}</th>
 | 
			
		||||
              <th>{{ capitalize(t('workouts.AVE_SPEED')) }}</th>
 | 
			
		||||
              <th>{{ capitalize(t('workouts.MAX_SPEED')) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.WORKOUT', 1)) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.DATE')) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.DISTANCE')) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.DURATION')) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.AVE_SPEED')) }}</th>
 | 
			
		||||
              <th>{{ capitalize($t('workouts.MAX_SPEED')) }}</th>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </thead>
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr v-for="workout in workouts" :key="workout.id">
 | 
			
		||||
              <td class="sport-col">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.SPORT', 1) }}
 | 
			
		||||
                  {{ $t('workouts.SPORT', 1) }}
 | 
			
		||||
                </span>
 | 
			
		||||
                <SportImage
 | 
			
		||||
                  :title="
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="workout-title">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ capitalize(t('workouts.WORKOUT', 1)) }}
 | 
			
		||||
                  {{ capitalize($t('workouts.WORKOUT', 1)) }}
 | 
			
		||||
                </span>
 | 
			
		||||
                <router-link
 | 
			
		||||
                  class="nav-item"
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
              </td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.DATE') }}
 | 
			
		||||
                  {{ $t('workouts.DATE') }}
 | 
			
		||||
                </span>
 | 
			
		||||
                {{
 | 
			
		||||
                  format(
 | 
			
		||||
@@ -64,25 +64,25 @@
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="text-right">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.DISTANCE') }}
 | 
			
		||||
                  {{ $t('workouts.DISTANCE') }}
 | 
			
		||||
                </span>
 | 
			
		||||
                {{ Number(workout.distance).toFixed(2) }} km
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="text-right">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.DURATION') }}
 | 
			
		||||
                  {{ $t('workouts.DURATION') }}
 | 
			
		||||
                </span>
 | 
			
		||||
                {{ workout.moving }}
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="text-right">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.AVE_SPEED') }}
 | 
			
		||||
                  {{ $t('workouts.AVE_SPEED') }}
 | 
			
		||||
                </span>
 | 
			
		||||
                {{ workout.ave_speed }} km/h
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="text-right">
 | 
			
		||||
                <span class="cell-heading">
 | 
			
		||||
                  {{ t('workouts.MAX_SPEED') }}
 | 
			
		||||
                  {{ $t('workouts.MAX_SPEED') }}
 | 
			
		||||
                </span>
 | 
			
		||||
                {{ workout.max_speed }} km/h
 | 
			
		||||
              </td>
 | 
			
		||||
@@ -94,7 +94,7 @@
 | 
			
		||||
    <NoWorkouts v-if="workouts.length === 0" />
 | 
			
		||||
    <div v-if="moreWorkoutsExist" class="more-workouts">
 | 
			
		||||
      <button @click="loadMoreWorkouts">
 | 
			
		||||
        {{ t('workouts.LOAD_MORE_WORKOUT') }}
 | 
			
		||||
        {{ $t('workouts.LOAD_MORE_WORKOUT') }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="bottom" />
 | 
			
		||||
@@ -112,9 +112,8 @@
 | 
			
		||||
    watch,
 | 
			
		||||
    onBeforeMount,
 | 
			
		||||
  } from 'vue'
 | 
			
		||||
  import { useI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
  import SportImage from '@/components/Common/SportImage/index.vue'
 | 
			
		||||
  import SportImage from '@/components/Common/Images/SportImage/index.vue'
 | 
			
		||||
  import StaticMap from '@/components/Common/StaticMap.vue'
 | 
			
		||||
  import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
 | 
			
		||||
  import { WORKOUTS_STORE } from '@/store/constants'
 | 
			
		||||
@@ -147,7 +146,6 @@
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const store = useStore()
 | 
			
		||||
      const { t } = useI18n()
 | 
			
		||||
      const workouts: ComputedRef<IWorkout[]> = computed(
 | 
			
		||||
        () => store.getters[WORKOUTS_STORE.GETTERS.USER_WORKOUTS]
 | 
			
		||||
      )
 | 
			
		||||
@@ -189,7 +187,6 @@
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        moreWorkoutsExist,
 | 
			
		||||
        t,
 | 
			
		||||
        workouts,
 | 
			
		||||
        capitalize,
 | 
			
		||||
        format,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "CONFIRMATION": "Confirmation",
 | 
			
		||||
  "DAY": "day | days",
 | 
			
		||||
  "HOME": "Home"
 | 
			
		||||
  "HOME": "Home",
 | 
			
		||||
  "HERE": "here"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "CONFIRM_ACCOUNT_DELETION": "Are you sure you want to delete your account? All data will be deleted, this cannot be undone",
 | 
			
		||||
  "EMAIL": "Email",
 | 
			
		||||
  "ENTER_EMAIL": "Enter an email address",
 | 
			
		||||
  "ENTER_PASSWORD": "Enter a password",
 | 
			
		||||
  "ENTER_PASSWORD_CONFIRMATION": "Confirm the password",
 | 
			
		||||
  "INVALID_TOKEN": "Invalid token, please request a new password reset.",
 | 
			
		||||
  "LANGUAGE": "Language",
 | 
			
		||||
  "LOGIN": "Login",
 | 
			
		||||
  "LOGOUT": "Logout",
 | 
			
		||||
  "PASSWORD": "Password",
 | 
			
		||||
  "PASSWORD_CONFIRM": "Confirm Password",
 | 
			
		||||
  "PASSWORD_CONFIRMATION": "Password confirmation",
 | 
			
		||||
  "PASSWORD_FORGOTTEN": "Forgot password?",
 | 
			
		||||
  "PASSWORD_RESET": "Password reset",
 | 
			
		||||
  "PASSWORD_SENT_EMAIL_TEXT": "Check your email. If your address is in our database, you'll received an email with a link to reset your password.",
 | 
			
		||||
  "PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
 | 
			
		||||
  "PROFILE": {
 | 
			
		||||
    "BACK_TO_PROFILE": "Back to profile",
 | 
			
		||||
    "BIO": "Bio",
 | 
			
		||||
@@ -36,6 +44,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "REGISTER": "Register",
 | 
			
		||||
  "REGISTER_DISABLED": "Sorry, registration is disabled.",
 | 
			
		||||
  "RESET_PASSWORD": "Reset your password",
 | 
			
		||||
  "USER_PICTURE": "user picture",
 | 
			
		||||
  "USERNAME": "Username"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "CONFIRMATION": "Confirmation",
 | 
			
		||||
  "DAY": "jour | jours",
 | 
			
		||||
  "HOME": "Accueil"
 | 
			
		||||
  "HOME": "Accueil",
 | 
			
		||||
  "HERE": "ici"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "CONFIRM_ACCOUNT_DELETION": "Etes-vous sûr de vouloir supprimer votre compte ? Toutes les données seront définitivement effacés.",
 | 
			
		||||
  "EMAIL": "Email",
 | 
			
		||||
  "ENTER_EMAIL": "Saisir une adresse email",
 | 
			
		||||
  "ENTER_PASSWORD": "Saisir un mot de passe",
 | 
			
		||||
  "ENTER_PASSWORD_CONFIRMATION": "Confirmer le mot de passe",
 | 
			
		||||
  "INVALID_TOKEN": "Jeton invalide, veullez demander une nouvelle réinitialisation de mot de passe.",
 | 
			
		||||
  "LANGUAGE": "Langue",
 | 
			
		||||
  "LOGIN": "Se connecter",
 | 
			
		||||
  "LOGOUT": "Se déconnecter",
 | 
			
		||||
  "PASSWORD": "Mot de passe",
 | 
			
		||||
  "PASSWORD_CONFIRM": "Confirmation du mot de passe",
 | 
			
		||||
  "PASSWORD_CONFIRMATION": "Confirmation du mot de passe",
 | 
			
		||||
  "PASSWORD_FORGOTTEN": "Mot de passe oublié ?",
 | 
			
		||||
  "PASSWORD_RESET": "Réinitialisation du mot de passe",
 | 
			
		||||
  "PASSWORD_SENT_EMAIL_TEXT": "Vérifiez vore boite mail. Si vote adresse est dans notre base de données, vous recevrez un email avec un lien pour réinitialiser votre mot de passe.",
 | 
			
		||||
  "PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
 | 
			
		||||
  "PROFILE": {
 | 
			
		||||
    "BACK_TO_PROFILE": "Revenir au profil",
 | 
			
		||||
    "BIO": "Bio",
 | 
			
		||||
@@ -36,6 +44,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "REGISTER": "S'inscrire",
 | 
			
		||||
  "REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.",
 | 
			
		||||
  "RESET_PASSWORD": "Réinitialiser votre mot de passe",
 | 
			
		||||
  "USER_PICTURE": "photo de l'utilisateur",
 | 
			
		||||
  "USERNAME": "Nom d'utilisateur"
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ import AddWorkout from '@/views/AddWorkout.vue'
 | 
			
		||||
import Dashboard from '@/views/DashBoard.vue'
 | 
			
		||||
import LoginOrRegister from '@/views/LoginOrRegister.vue'
 | 
			
		||||
import NotFoundView from '@/views/NotFoundView.vue'
 | 
			
		||||
import PasswordResetView from '@/views/PasswordResetView.vue'
 | 
			
		||||
import ProfileView from '@/views/ProfileView.vue'
 | 
			
		||||
import StatisticsView from '@/views/StatisticsView.vue'
 | 
			
		||||
import EditWorkout from '@/views/workouts/EditWorkout.vue'
 | 
			
		||||
@@ -36,6 +37,30 @@ const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
    component: ProfileView,
 | 
			
		||||
    props: { edition: false, tab: 'PROFILE' },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/password-reset/sent',
 | 
			
		||||
    name: 'PasswordEmailSent',
 | 
			
		||||
    component: PasswordResetView,
 | 
			
		||||
    props: { action: 'request-sent' },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/password-reset/request',
 | 
			
		||||
    name: 'PasswordResetRequest',
 | 
			
		||||
    component: PasswordResetView,
 | 
			
		||||
    props: { action: 'reset-request' },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/password-reset/password-updated',
 | 
			
		||||
    name: 'PasswordUpdated',
 | 
			
		||||
    component: PasswordResetView,
 | 
			
		||||
    props: { action: 'password-updated' },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/password-reset',
 | 
			
		||||
    name: 'PasswordReset',
 | 
			
		||||
    component: PasswordResetView,
 | 
			
		||||
    props: { action: 'reset' },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/profile/edit/picture',
 | 
			
		||||
    name: 'UserPictureEdition',
 | 
			
		||||
@@ -100,18 +125,27 @@ const router = createRouter({
 | 
			
		||||
  routes,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const pathsWithoutAuthentication = [
 | 
			
		||||
  '/login',
 | 
			
		||||
  '/password-reset',
 | 
			
		||||
  '/password-reset/password-updated',
 | 
			
		||||
  '/password-reset/request',
 | 
			
		||||
  '/password-reset/sent',
 | 
			
		||||
  '/register',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
router.beforeEach((to, from, next) => {
 | 
			
		||||
  store
 | 
			
		||||
    .dispatch(USER_STORE.ACTIONS.CHECK_AUTH_USER)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      if (
 | 
			
		||||
        store.getters[USER_STORE.GETTERS.IS_AUTHENTICATED] &&
 | 
			
		||||
        ['/login', '/register'].includes(to.path)
 | 
			
		||||
        pathsWithoutAuthentication.includes(to.path)
 | 
			
		||||
      ) {
 | 
			
		||||
        return next('/')
 | 
			
		||||
      } else if (
 | 
			
		||||
        !store.getters[USER_STORE.GETTERS.IS_AUTHENTICATED] &&
 | 
			
		||||
        !['/login', '/register'].includes(to.path)
 | 
			
		||||
        !pathsWithoutAuthentication.includes(to.path)
 | 
			
		||||
      ) {
 | 
			
		||||
        const path =
 | 
			
		||||
          to.path === '/'
 | 
			
		||||
 
 | 
			
		||||
@@ -62,4 +62,6 @@
 | 
			
		||||
  --cell-heading-bg-color: #eeeeee;
 | 
			
		||||
  --cell-heading-color: #696969;
 | 
			
		||||
 | 
			
		||||
  --svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color))
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -16,6 +16,8 @@ import { IUserActions, IUserState } from '@/store/modules/user/types'
 | 
			
		||||
import {
 | 
			
		||||
  ILoginOrRegisterData,
 | 
			
		||||
  IUserDeletionPayload,
 | 
			
		||||
  IUserPasswordPayload,
 | 
			
		||||
  IUserPasswordResetPayload,
 | 
			
		||||
  IUserPayload,
 | 
			
		||||
  IUserPicturePayload,
 | 
			
		||||
  IUserPreferencesPayload,
 | 
			
		||||
@@ -214,4 +216,36 @@ export const actions: ActionTree<IUserState, IRootState> & IUserActions = {
 | 
			
		||||
        context.commit(USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
 | 
			
		||||
      )
 | 
			
		||||
  },
 | 
			
		||||
  [USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST](
 | 
			
		||||
    context: ActionContext<IUserState, IRootState>,
 | 
			
		||||
    payload: IUserPasswordPayload
 | 
			
		||||
  ): void {
 | 
			
		||||
    context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
 | 
			
		||||
    api
 | 
			
		||||
      .post('auth/password/reset-request', payload)
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        if (res.data.status === 'success') {
 | 
			
		||||
          router.push('/password-reset/sent')
 | 
			
		||||
        } else {
 | 
			
		||||
          handleError(context, null)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch((error) => handleError(context, error))
 | 
			
		||||
  },
 | 
			
		||||
  [USER_STORE.ACTIONS.RESET_USER_PASSWORD](
 | 
			
		||||
    context: ActionContext<IUserState, IRootState>,
 | 
			
		||||
    payload: IUserPasswordResetPayload
 | 
			
		||||
  ): void {
 | 
			
		||||
    context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
 | 
			
		||||
    api
 | 
			
		||||
      .post('auth/password/update', payload)
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        if (res.data.status === 'success') {
 | 
			
		||||
          router.push('/password-reset/password-updated')
 | 
			
		||||
        } else {
 | 
			
		||||
          handleError(context, null)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch((error) => handleError(context, error))
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ export enum UserActions {
 | 
			
		||||
  GET_USER_PROFILE = 'GET_USER_PROFILE',
 | 
			
		||||
  LOGIN_OR_REGISTER = 'LOGIN_OR_REGISTER',
 | 
			
		||||
  LOGOUT = 'LOGOUT',
 | 
			
		||||
  SEND_PASSWORD_RESET_REQUEST = 'SEND_PASSWORD_RESET_REQUEST',
 | 
			
		||||
  RESET_USER_PASSWORD = 'RESET_USER_PASSWORD',
 | 
			
		||||
  UPDATE_USER_PICTURE = 'UPDATE_USER_PICTURE',
 | 
			
		||||
  UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE',
 | 
			
		||||
  UPDATE_USER_PREFERENCES = 'UPDATE_USER_PREFERENCES',
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ import {
 | 
			
		||||
  IAuthUserProfile,
 | 
			
		||||
  ILoginOrRegisterData,
 | 
			
		||||
  IUserDeletionPayload,
 | 
			
		||||
  IUserPasswordPayload,
 | 
			
		||||
  IUserPasswordResetPayload,
 | 
			
		||||
  IUserPayload,
 | 
			
		||||
  IUserPicturePayload,
 | 
			
		||||
  IUserPreferencesPayload,
 | 
			
		||||
@@ -55,6 +57,16 @@ export interface IUserActions {
 | 
			
		||||
    payload: IUserPicturePayload
 | 
			
		||||
  ): void
 | 
			
		||||
 | 
			
		||||
  [USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST](
 | 
			
		||||
    context: ActionContext<IUserState, IRootState>,
 | 
			
		||||
    payload: IUserPasswordPayload
 | 
			
		||||
  ): void
 | 
			
		||||
 | 
			
		||||
  [USER_STORE.ACTIONS.RESET_USER_PASSWORD](
 | 
			
		||||
    context: ActionContext<IUserState, IRootState>,
 | 
			
		||||
    payload: IUserPasswordResetPayload
 | 
			
		||||
  ): void
 | 
			
		||||
 | 
			
		||||
  [USER_STORE.ACTIONS.DELETE_ACCOUNT](
 | 
			
		||||
    context: ActionContext<IUserState, IRootState>,
 | 
			
		||||
    payload: IUserDeletionPayload
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,16 @@ export interface IUserPicturePayload {
 | 
			
		||||
  picture: File
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUserPasswordPayload {
 | 
			
		||||
  email: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUserPasswordResetPayload {
 | 
			
		||||
  password: string
 | 
			
		||||
  password_conf: string
 | 
			
		||||
  token: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUserDeletionPayload {
 | 
			
		||||
  username: string
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
  import { defineComponent } from 'vue'
 | 
			
		||||
 | 
			
		||||
  import BikePic from '@/components/BikePic.vue'
 | 
			
		||||
  import LoginOrRegisterForm from '@/components/User/LoginOrRegisterForm.vue'
 | 
			
		||||
  import LoginOrRegisterForm from '@/components/User/UserAuthForm.vue'
 | 
			
		||||
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'NavBar',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								fittrackee_client/src/views/PasswordResetView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								fittrackee_client/src/views/PasswordResetView.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div id="password-reset">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
      <PasswordResetRequest
 | 
			
		||||
        v-if="action.startsWith('reset')"
 | 
			
		||||
        :action="action"
 | 
			
		||||
        :token="token"
 | 
			
		||||
      />
 | 
			
		||||
      <PasswordEmailSent v-else :action="action" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { computed, defineComponent, onBeforeMount } from 'vue'
 | 
			
		||||
  import { useRoute, useRouter } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
  import PasswordEmailSent from '@/components/User/PasswordReset/PasswordActionDone.vue'
 | 
			
		||||
  import PasswordResetRequest from '@/components/User/PasswordReset/PasswordResetForm.vue'
 | 
			
		||||
  export default defineComponent({
 | 
			
		||||
    name: 'PasswordResetView',
 | 
			
		||||
    components: {
 | 
			
		||||
      PasswordEmailSent,
 | 
			
		||||
      PasswordResetRequest,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
      action: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup(props) {
 | 
			
		||||
      const route = useRoute()
 | 
			
		||||
      const router = useRouter()
 | 
			
		||||
      const token = computed(() => route.query.token)
 | 
			
		||||
 | 
			
		||||
      onBeforeMount(() => {
 | 
			
		||||
        if (props.action === 'reset' && !token.value) {
 | 
			
		||||
          router.push('/')
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      return { token }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
  @import '~@/scss/base.scss';
 | 
			
		||||
 | 
			
		||||
  #password-reset {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    .container {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      width: 50%;
 | 
			
		||||
 | 
			
		||||
      @media screen and (max-width: $small-limit) {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        margin: 0 auto 50px auto;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
            aria-hidden="true"
 | 
			
		||||
          />
 | 
			
		||||
          <span>
 | 
			
		||||
            {{ t(`workouts.${hiddenFilters ? 'DISPLAY' : 'HIDE'}_FILTERS`) }}
 | 
			
		||||
            {{ $t(`workouts.${hiddenFilters ? 'DISPLAY' : 'HIDE'}_FILTERS`) }}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -71,7 +71,6 @@
 | 
			
		||||
        authUser,
 | 
			
		||||
        hiddenFilters,
 | 
			
		||||
        params,
 | 
			
		||||
        t,
 | 
			
		||||
        translatedSports,
 | 
			
		||||
        toggleFilters,
 | 
			
		||||
        updateParams,
 | 
			
		||||
 
 | 
			
		||||
@@ -976,6 +976,18 @@
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
    "@intlify/vue-devtools" "9.1.7"
 | 
			
		||||
 | 
			
		||||
"@intlify/core-base@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.1.9.tgz#e4e8c951010728e4af3a0d13d74cf3f9e7add7f6"
 | 
			
		||||
  integrity sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/devtools-if" "9.1.9"
 | 
			
		||||
    "@intlify/message-compiler" "9.1.9"
 | 
			
		||||
    "@intlify/message-resolver" "9.1.9"
 | 
			
		||||
    "@intlify/runtime" "9.1.9"
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
    "@intlify/vue-devtools" "9.1.9"
 | 
			
		||||
 | 
			
		||||
"@intlify/core@^9.1.6":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/core/-/core-9.1.7.tgz#69c00dc31111f1b61d79fbd9ad1838196e73c94a"
 | 
			
		||||
@@ -990,6 +1002,13 @@
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
 | 
			
		||||
"@intlify/devtools-if@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.1.9.tgz#a30e1dd1256ff2c5c98d8d75d075384fba898e5d"
 | 
			
		||||
  integrity sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
 | 
			
		||||
"@intlify/message-compiler@9.1.7", "@intlify/message-compiler@^9.1.6":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.1.7.tgz#4663fcc2a190f3cc6970e12565c8d6f22beeb719"
 | 
			
		||||
@@ -999,11 +1018,25 @@
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
    source-map "0.6.1"
 | 
			
		||||
 | 
			
		||||
"@intlify/message-compiler@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.1.9.tgz#1193cbd224a71c2fb981455b8534a3c766d2948d"
 | 
			
		||||
  integrity sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/message-resolver" "9.1.9"
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
    source-map "0.6.1"
 | 
			
		||||
 | 
			
		||||
"@intlify/message-resolver@9.1.7":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/message-resolver/-/message-resolver-9.1.7.tgz#a95d13866c8de85784358039c8845668152e4162"
 | 
			
		||||
  integrity sha512-WTK+OaXJYjyquLGhuCyDvU2WHkG+kXzXeHagmVFHn+s118Jf2143zzkLLUrapP5CtZ/csuyjmYg7b3xQRQAmvw==
 | 
			
		||||
 | 
			
		||||
"@intlify/message-resolver@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/message-resolver/-/message-resolver-9.1.9.tgz#3155ccd2f5e6d0dc16cad8b7f1d8e97fcda05bfc"
 | 
			
		||||
  integrity sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==
 | 
			
		||||
 | 
			
		||||
"@intlify/runtime@9.1.7":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/runtime/-/runtime-9.1.7.tgz#67e0d6b2fd85a5b0b301a151c2f436f93154c3c6"
 | 
			
		||||
@@ -1013,11 +1046,25 @@
 | 
			
		||||
    "@intlify/message-resolver" "9.1.7"
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
 | 
			
		||||
"@intlify/runtime@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/runtime/-/runtime-9.1.9.tgz#2c12ce29518a075629efed0a8ed293ee740cb285"
 | 
			
		||||
  integrity sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/message-compiler" "9.1.9"
 | 
			
		||||
    "@intlify/message-resolver" "9.1.9"
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
 | 
			
		||||
"@intlify/shared@9.1.7", "@intlify/shared@^9.1.6":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.1.7.tgz#e7d8bc90cb59dc17dd7b4c85a73db16fcb7891fc"
 | 
			
		||||
  integrity sha512-zt0zlUdalumvT9AjQNxPXA36UgOndUyvBMplh8uRZU0fhWHAwhnJTcf0NaG9Qvr8I1n3HPSs96+kLb/YdwTavQ==
 | 
			
		||||
 | 
			
		||||
"@intlify/shared@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.1.9.tgz#0baaf96128b85560666bec784ffb01f6623cc17a"
 | 
			
		||||
  integrity sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==
 | 
			
		||||
 | 
			
		||||
"@intlify/vue-devtools@9.1.7":
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.1.7.tgz#b08d39bb5f21ba9b1954eab9466e9408129425a7"
 | 
			
		||||
@@ -1027,6 +1074,15 @@
 | 
			
		||||
    "@intlify/runtime" "9.1.7"
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
 | 
			
		||||
"@intlify/vue-devtools@9.1.9":
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.1.9.tgz#2be8f4dbe7f7ed4115676eb32348141d411e426b"
 | 
			
		||||
  integrity sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/message-resolver" "9.1.9"
 | 
			
		||||
    "@intlify/runtime" "9.1.9"
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
 | 
			
		||||
"@intlify/vue-i18n-loader@^2.1.0":
 | 
			
		||||
  version "2.1.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-loader/-/vue-i18n-loader-2.1.2.tgz#91a0858e26275dfc2c9c27aef9883028cada45ae"
 | 
			
		||||
@@ -9877,14 +9933,14 @@ vue-i18n@^8.17.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.25.0.tgz#1037d9295fa2845a230b771de473481edb2cfc4c"
 | 
			
		||||
  integrity sha512-ynhcL+PmTxuuSE1T10htiSXzjBozxYIE3ffbM1RfgAkVbr/v1SP+9Mi/7/uv8ZVV1yGuKjFAYp9BXq+X7op6MQ==
 | 
			
		||||
 | 
			
		||||
vue-i18n@^9.1.0:
 | 
			
		||||
  version "9.1.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.1.7.tgz#6f28dd2135197066508e2e65ab204a019750d773"
 | 
			
		||||
  integrity sha512-ujuuDanoHqtEd4GejWrbG/fXE9nrP51ElsEGxp0WBHfv+/ki0/wyUqkO+4fLikki2obGtXdviTPH0VNpas5K6g==
 | 
			
		||||
vue-i18n@^9.1.9:
 | 
			
		||||
  version "9.1.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.1.9.tgz#cb53e06ab5cc5b7eed59332f151caf48d47be9bb"
 | 
			
		||||
  integrity sha512-JeRdNVxS2OGp1E+pye5XB6+M6BBkHwAv9C80Q7+kzoMdUDGRna06tjC0vCB/jDX9aWrl5swxOMFcyAr7or8XTA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@intlify/core-base" "9.1.7"
 | 
			
		||||
    "@intlify/shared" "9.1.7"
 | 
			
		||||
    "@intlify/vue-devtools" "9.1.7"
 | 
			
		||||
    "@intlify/core-base" "9.1.9"
 | 
			
		||||
    "@intlify/shared" "9.1.9"
 | 
			
		||||
    "@intlify/vue-devtools" "9.1.9"
 | 
			
		||||
    "@vue/devtools-api" "^6.0.0-beta.7"
 | 
			
		||||
 | 
			
		||||
"vue-loader-v16@npm:vue-loader@^16.1.0":
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user