Merged dev branch
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fittrackee_client",
|
||||
"version": "0.5.7",
|
||||
"version": "0.6.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
@ -11,50 +11,54 @@
|
||||
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tmcw/togeojson": "^4.5.0",
|
||||
"@tmcw/togeojson": "^5.2.1",
|
||||
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
||||
"axios": "^0.25.0",
|
||||
"chart.js": "^3.7.0",
|
||||
"@zxcvbn-ts/core": "^2.0.1",
|
||||
"@zxcvbn-ts/language-common": "^2.0.1",
|
||||
"@zxcvbn-ts/language-en": "^2.0.1",
|
||||
"@zxcvbn-ts/language-fr": "^2.1.0",
|
||||
"axios": "^0.26.1",
|
||||
"chart.js": "^3.8.0",
|
||||
"chartjs-plugin-datalabels": "^2.0.0",
|
||||
"core-js": "^3.21.0",
|
||||
"core-js": "^3.23.4",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns-tz": "^1.2.2",
|
||||
"leaflet": "^1.7.1",
|
||||
"date-fns-tz": "^1.3.6",
|
||||
"leaflet": "^1.8.0",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-chart-3": "^3.0.9",
|
||||
"vue": "^3.2.37",
|
||||
"vue-chart-3": "3.1.1",
|
||||
"vue-fullscreen": "^3.1.1",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue-i18n": "^9.1.10",
|
||||
"vue-router": "^4.1.2",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/vue-i18n-loader": "^4.0.1",
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-plugin-unit-mocha": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"@intlify/vue-i18n-loader": "^4.2.0",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-plugin-pwa": "~5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.8",
|
||||
"@vue/cli-plugin-typescript": "~5.0.8",
|
||||
"@vue/cli-plugin-unit-mocha": "~5.0.8",
|
||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/test-utils": "^2.0.2",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-import-resolver-typescript": "^2.4.0",
|
||||
"eslint-plugin-import": "^2.24.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^10.2.0",
|
||||
"typescript": "~4.4.4",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.2.5",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"typescript": "^4.7.4",
|
||||
"vue-cli-plugin-i18n": "~2.3.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
@ -66,8 +70,6 @@
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
@ -81,7 +83,8 @@
|
||||
"import/resolver": "typescript"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
"ecmaVersion": 2020,
|
||||
"parser": "@typescript-eslint/parser"
|
||||
},
|
||||
"rules": {
|
||||
"import/order": [
|
||||
@ -93,7 +96,8 @@
|
||||
"caseInsensitive": true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"vue/multi-word-component-names": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@ -110,6 +114,7 @@
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
|
@ -19,7 +19,11 @@
|
||||
<i class="fa fa-chevron-up" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<Footer v-if="appConfig" :version="appConfig ? appConfig.version : ''" />
|
||||
<Footer
|
||||
v-if="appConfig"
|
||||
:version="appConfig ? appConfig.version : ''"
|
||||
:adminContact="appConfig.admin_contact"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -31,6 +35,7 @@
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { localeFromLanguage } from '@/utils/locales'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -43,7 +48,10 @@
|
||||
const hideScrollBar = ref(false)
|
||||
const displayScrollButton = ref(false)
|
||||
|
||||
onBeforeMount(() => store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG))
|
||||
onBeforeMount(() => {
|
||||
initLanguage()
|
||||
store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG)
|
||||
})
|
||||
onMounted(() => scroll())
|
||||
|
||||
function updateHideScrollBar(isMenuOpen: boolean) {
|
||||
@ -57,7 +65,7 @@
|
||||
}
|
||||
function scroll() {
|
||||
window.onscroll = () => {
|
||||
let bottom = document.querySelector('#bottom')
|
||||
const bottom = document.querySelector('#bottom')
|
||||
displayScrollButton.value = bottom !== null && isScrolledToBottom(bottom)
|
||||
}
|
||||
}
|
||||
@ -70,6 +78,18 @@
|
||||
displayScrollButton.value = false
|
||||
}, 300)
|
||||
}
|
||||
function initLanguage() {
|
||||
let language = 'en'
|
||||
try {
|
||||
const navigatorLanguage = navigator.language.split('-')[0]
|
||||
if (navigatorLanguage in localeFromLanguage) {
|
||||
language = navigatorLanguage
|
||||
}
|
||||
} catch (e) {
|
||||
language = 'en'
|
||||
}
|
||||
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE, language)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
72
fittrackee_client/src/components/About.vue
Normal file
72
fittrackee_client/src/components/About.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div class="about-text">
|
||||
<div>
|
||||
<p class="error-message" v-html="$t('about.FITTRACKEE_DESCRIPTION')" />
|
||||
<p>
|
||||
<i class="fa fa-book fa-padding" aria-hidden="true"></i>
|
||||
<a
|
||||
href="https://samr1.github.io/FitTrackee/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ capitalize($t('common.DOCUMENTATION')) }}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-github fa-padding" aria-hidden="true"></i>
|
||||
<a
|
||||
href="https://github.com/SamR1/FitTrackee"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ $t('about.SOURCE_CODE') }}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-balance-scale fa-padding" aria-hidden="true"></i>
|
||||
<i18n-t keypath="about.FITTRACKEE_LICENSE">
|
||||
<a
|
||||
href="https://choosealicense.com/licenses/agpl-3.0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
AGPLv3
|
||||
</a>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<div v-if="appConfig.admin_contact">
|
||||
<i class="fa fa-envelope-o fa-padding" aria-hidden="true"></i>
|
||||
<a :href="`mailto:${appConfig.admin_contact}`">
|
||||
{{ $t('about.CONTACT_ADMIN') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ComputedRef, computed, capitalize } from 'vue'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
const store = useStore()
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
|
||||
.about-text {
|
||||
margin-top: 200px;
|
||||
@media screen and (max-width: $small-limit) {
|
||||
margin-top: 0;
|
||||
}
|
||||
.fa-padding {
|
||||
padding-right: $default-padding;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,6 +4,23 @@
|
||||
<template #title>{{ $t('admin.APP_CONFIG.TITLE') }}</template>
|
||||
<template #content>
|
||||
<form class="admin-form" @submit.prevent="onSubmit">
|
||||
<label for="admin_contact">
|
||||
{{ $t('admin.APP_CONFIG.ADMIN_CONTACT') }}:
|
||||
<input
|
||||
class="no-contact"
|
||||
v-if="!edition && !appData.admin_contact"
|
||||
:value="$t('admin.APP_CONFIG.NO_CONTACT_EMAIL')"
|
||||
disabled
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
id="admin_contact"
|
||||
name="admin_contact"
|
||||
type="email"
|
||||
v-model="appData.admin_contact"
|
||||
:disabled="!edition"
|
||||
/>
|
||||
</label>
|
||||
<label for="max_users">
|
||||
{{ $t('admin.APP_CONFIG.MAX_USERS_LABEL') }}:
|
||||
<input
|
||||
@ -89,6 +106,7 @@
|
||||
reactive,
|
||||
withDefaults,
|
||||
onBeforeMount,
|
||||
toRefs,
|
||||
} from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@ -104,11 +122,13 @@
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
edition: false,
|
||||
})
|
||||
const { edition } = toRefs(props)
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const appData: TAppConfigForm = reactive({
|
||||
admin_contact: '',
|
||||
max_users: 0,
|
||||
max_single_file_size: 0,
|
||||
max_zip_file_size: 0,
|
||||
@ -126,13 +146,13 @@
|
||||
|
||||
function updateForm(appConfig: TAppConfig) {
|
||||
Object.keys(appData).map((key) => {
|
||||
;['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
})
|
||||
}
|
||||
function onCancel() {
|
||||
@ -160,4 +180,7 @@
|
||||
margin-right: $default-margin;
|
||||
}
|
||||
}
|
||||
.no-contact {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
@ -11,7 +11,7 @@
|
||||
{{ $t('admin.APPLICATION') }}
|
||||
</router-link>
|
||||
</dt>
|
||||
<dd>
|
||||
<dd class="application-config-details">
|
||||
{{ $t('admin.UPDATE_APPLICATION_DESCRIPTION') }}<br />
|
||||
<span class="registration-status">
|
||||
{{
|
||||
@ -22,6 +22,13 @@
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
class="email-sending-status"
|
||||
v-if="!appConfig.is_email_sending_enabled"
|
||||
>
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true" />
|
||||
{{ $t('admin.EMAIL_SENDING_DISABLED') }}
|
||||
</span>
|
||||
</dd>
|
||||
<dt>
|
||||
<router-link to="/admin/sports">
|
||||
@ -82,8 +89,13 @@
|
||||
dd {
|
||||
margin-bottom: $default-margin * 3;
|
||||
}
|
||||
.registration-status {
|
||||
font-weight: bold;
|
||||
.application-config-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.email-sending-status,
|
||||
.registration-status {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
<button class="top-button" @click.prevent="$router.push('/admin')">
|
||||
{{ $t('admin.BACK_TO_ADMIN') }}
|
||||
</button>
|
||||
<UsersNameFilter @filterOnUsername="searchUsers" />
|
||||
<FilterSelects
|
||||
:sort="sortList"
|
||||
:order_by="orderByList"
|
||||
@ -13,7 +14,10 @@
|
||||
message="admin.USERS.SELECTS.ORDER_BY"
|
||||
@updateSelect="reloadUsers"
|
||||
/>
|
||||
<div class="responsive-table">
|
||||
<div class="no-users" v-if="users.length === 0">
|
||||
{{ $t('user.NO_USERS_FOUND') }}
|
||||
</div>
|
||||
<div class="responsive-table" v-else>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -26,6 +30,7 @@
|
||||
<th>
|
||||
{{ capitalize($t('workouts.WORKOUT', 0)) }}
|
||||
</th>
|
||||
<th>{{ $t('admin.ACTIVE') }}</th>
|
||||
<th>{{ $t('user.ADMIN') }}</th>
|
||||
<th>{{ $t('admin.ACTION') }}</th>
|
||||
</tr>
|
||||
@ -42,7 +47,7 @@
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.USERNAME') }}
|
||||
</span>
|
||||
<router-link :to="`/users/${user.username}`">
|
||||
<router-link :to="`/admin/users/${user.username}`">
|
||||
{{ user.username }}
|
||||
</router-link>
|
||||
</td>
|
||||
@ -69,6 +74,15 @@
|
||||
</span>
|
||||
{{ user.nb_workouts }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="cell-heading">
|
||||
{{ $t('admin.ACTIVE') }}
|
||||
</span>
|
||||
<i
|
||||
:class="`fa fa${user.is_active ? '-check' : ''}-square-o`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.ADMIN') }}
|
||||
@ -119,6 +133,7 @@
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
reactive,
|
||||
watch,
|
||||
@ -131,9 +146,10 @@
|
||||
import FilterSelects from '@/components/Common/FilterSelects.vue'
|
||||
import Pagination from '@/components/Common/Pagination.vue'
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import UsersNameFilter from '@/components/Users/UsersNameFilter.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE, USERS_STORE } from '@/store/constants'
|
||||
import { IPagination, TPaginationPayload } from '@/types/api'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile, IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getQuery, sortList } from '@/utils/api'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
@ -143,6 +159,7 @@
|
||||
const router = useRouter()
|
||||
|
||||
const orderByList: string[] = [
|
||||
'is_active',
|
||||
'admin',
|
||||
'created_at',
|
||||
'username',
|
||||
@ -152,7 +169,7 @@
|
||||
let query: TPaginationPayload = reactive(
|
||||
getQuery(route.query, orderByList, defaultOrderBy)
|
||||
)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
const authUser: ComputedRef<IAuthUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const users: ComputedRef<IUserProfile[]> = computed(
|
||||
@ -170,6 +187,10 @@
|
||||
function loadUsers(queryParams: TPaginationPayload) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
|
||||
}
|
||||
function searchUsers(username: Ref<string>) {
|
||||
reloadUsers('q', username.value)
|
||||
}
|
||||
|
||||
function updateUser(username: string, admin: boolean) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
@ -203,6 +224,14 @@
|
||||
.top-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-users {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: $default-padding * 2 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
td {
|
||||
font-size: 1.1em;
|
||||
|
@ -10,12 +10,17 @@
|
||||
#bike {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
margin-top: 180px;
|
||||
padding: $default-padding;
|
||||
height: 100%;
|
||||
|
||||
.bike-img {
|
||||
max-width: 40%;
|
||||
max-width: 200px;
|
||||
}
|
||||
@media screen and (max-width: $small-limit) {
|
||||
margin-top: $default-margin;
|
||||
.bike-img {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
const emit = defineEmits(['updateValue'])
|
||||
|
||||
let text = ref('')
|
||||
const text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
|
@ -33,8 +33,8 @@
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
let isOpen = ref(false)
|
||||
let dropdownOptions = props.options.map((option) => option)
|
||||
const isOpen = ref(false)
|
||||
const dropdownOptions = props.options.map((option) => option)
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen.value = !isOpen.value
|
||||
|
28
fittrackee_client/src/components/Common/Images/ErrorImg.vue
Normal file
28
fittrackee_client/src/components/Common/Images/ErrorImg.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -51 512 512">
|
||||
<g id="error">
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 0 0 C 0 11.300781 0 399.777344 0 410 L 512 410 C 512 402.324219 512 2.425781 512 0 Z M 370 71 L 370 30 L 411 30 L 411 71 Z M 30 30 L 340 30 L 340 71 L 30 71 Z M 482 380 L 30 380 L 30 101 L 482 101 Z M 441 71 L 441 30 L 482 30 L 482 71 Z M 441 71 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 325.519531 297.070312 C 294.328125 265.878906 294.328125 215.125 325.519531 183.929688 L 304.304688 162.71875 C 261.417969 205.605469 261.417969 275.390625 304.304688 318.28125 Z M 325.519531 297.070312 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 197.089844 180 L 237.089844 180 L 237.089844 220 L 197.089844 220 Z M 197.089844 180 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 197.089844 261 L 237.089844 261 L 237.089844 301 L 197.089844 301 Z M 197.089844 261 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorImg',
|
||||
}
|
||||
</script>
|
@ -3,13 +3,15 @@
|
||||
<ul class="pagination">
|
||||
<li class="page-prev" :class="{ disabled: !pagination.has_prev }">
|
||||
<router-link
|
||||
v-slot="{ navigate }"
|
||||
class="page-link"
|
||||
:to="{ path, query: getQuery(pagination.page, -1) }"
|
||||
:event="pagination.has_prev ? 'click' : ''"
|
||||
:disabled="!pagination.has_prev"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
{{ $t('api.PAGINATION.PREVIOUS') }}
|
||||
<slot @click="pagination.has_next ? navigate : null">
|
||||
{{ $t('api.PAGINATION.PREVIOUS') }}
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</slot>
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
@ -29,13 +31,15 @@
|
||||
</li>
|
||||
<li class="page-next" :class="{ disabled: !pagination.has_next }">
|
||||
<router-link
|
||||
v-slot="{ navigate }"
|
||||
class="page-link"
|
||||
:to="{ path, query: getQuery(pagination.page, 1) }"
|
||||
:event="pagination.has_next ? 'click' : ''"
|
||||
:disabled="!pagination.has_next"
|
||||
>
|
||||
{{ $t('api.PAGINATION.NEXT') }}
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
<slot @click="pagination.has_next ? navigate : null">
|
||||
{{ $t('api.PAGINATION.NEXT') }}
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</slot>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
@ -45,20 +49,23 @@
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IPagination } from '@/types/api'
|
||||
import { IPagination, TPaginationPayload } from '@/types/api'
|
||||
import { TWorkoutsPayload } from '@/types/workouts'
|
||||
import { rangePagination } from '@/utils/api'
|
||||
|
||||
interface Props {
|
||||
pagination: IPagination
|
||||
path: string
|
||||
query: TWorkoutsPayload
|
||||
query: TWorkoutsPayload | TPaginationPayload
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { pagination, path, query } = toRefs(props)
|
||||
|
||||
function getQuery(page: number, cursor?: number): TWorkoutsPayload {
|
||||
function getQuery(
|
||||
page: number,
|
||||
cursor?: number
|
||||
): TWorkoutsPayload | TPaginationPayload {
|
||||
const newQuery = Object.assign({}, query.value)
|
||||
newQuery.page = cursor ? page + cursor : page
|
||||
return newQuery
|
||||
@ -92,6 +99,8 @@
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
a {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
color: var(--disabled-color);
|
||||
}
|
||||
}
|
||||
|
95
fittrackee_client/src/components/Common/PasswordInput.vue
Normal file
95
fittrackee_client/src/components/Common/PasswordInput.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="password-input">
|
||||
<input
|
||||
:id="id"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
v-model="passwordValue"
|
||||
minlength="8"
|
||||
@input="updatePassword"
|
||||
@invalid="invalidPassword"
|
||||
/>
|
||||
<div class="show-password" @click="togglePassword">
|
||||
{{ $t(`user.${showPassword ? 'HIDE' : 'SHOW'}_PASSWORD`) }}
|
||||
<i
|
||||
class="fa"
|
||||
:class="`fa-eye${showPassword ? '-slash' : ''}`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="checkStrength" class="form-info">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" />
|
||||
{{ $t('user.PASSWORD_INFO') }}
|
||||
</div>
|
||||
<PasswordStrength v-if="checkStrength" :password="passwordValue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref, toRefs, watch, withDefaults } from 'vue'
|
||||
|
||||
import PasswordStrength from '@/components/Common/PasswordStength.vue'
|
||||
|
||||
interface Props {
|
||||
checkStrength?: boolean
|
||||
disabled?: boolean
|
||||
id?: string
|
||||
password?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
checkStrength: false,
|
||||
disabled: false,
|
||||
id: 'password',
|
||||
password: '',
|
||||
required: false,
|
||||
})
|
||||
const { checkStrength, disabled, id, password, placeholder, required } =
|
||||
toRefs(props)
|
||||
|
||||
const showPassword: Ref<boolean> = ref(false)
|
||||
const passwordValue: Ref<string> = ref('')
|
||||
|
||||
const emit = defineEmits(['updatePassword', 'passwordError'])
|
||||
|
||||
function togglePassword() {
|
||||
showPassword.value = !showPassword.value
|
||||
}
|
||||
function updatePassword(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updatePassword', event.target.value)
|
||||
}
|
||||
function invalidPassword() {
|
||||
emit('passwordError')
|
||||
}
|
||||
|
||||
watch(
|
||||
() => password.value,
|
||||
(newPassword) => {
|
||||
if (newPassword === '') {
|
||||
passwordValue.value = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.password-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.show-password {
|
||||
font-style: italic;
|
||||
font-size: 0.85em;
|
||||
text-align: right;
|
||||
margin-top: -0.75 * $default-margin;
|
||||
padding-right: $default-padding;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
164
fittrackee_client/src/components/Common/PasswordStength.vue
Normal file
164
fittrackee_client/src/components/Common/PasswordStength.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="password-strength">
|
||||
<input
|
||||
class="password-slider"
|
||||
:class="`strength-${passwordScore}`"
|
||||
:style="{ backgroundSize: backgroundSize }"
|
||||
type="range"
|
||||
:value="passwordScore"
|
||||
min="0"
|
||||
max="4"
|
||||
step="1"
|
||||
/>
|
||||
<div v-if="passwordStrength" class="password-strength-details">
|
||||
<span class="password-strength-value">
|
||||
{{ $t('user.PASSWORD_STRENGTH.LABEL') }}:
|
||||
{{ $t(`user.PASSWORD_STRENGTH.${passwordStrength}`) }}
|
||||
</span>
|
||||
<div class="info-box" v-if="passwordSuggestions.length > 0">
|
||||
<ul class="password-feedback">
|
||||
<li v-for="suggestion in passwordSuggestions" :key="suggestion">
|
||||
{{ $t(`user.PASSWORD_STRENGTH.SUGGESTIONS.${suggestion}`) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { zxcvbn } from '@zxcvbn-ts/core'
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
toRefs,
|
||||
watch,
|
||||
} from 'vue'
|
||||
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getPasswordStrength, setZxcvbnOptions } from '@/utils/password'
|
||||
|
||||
interface Props {
|
||||
password: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { password } = toRefs(props)
|
||||
|
||||
const store = useStore()
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
const isSuccess: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_SUCCESS]
|
||||
)
|
||||
const passwordScore: Ref<number> = ref(0)
|
||||
const passwordStrength: Ref<string> = ref('')
|
||||
const passwordSuggestions: Ref<string[]> = ref([])
|
||||
const backgroundSize = ref('0% 100%')
|
||||
|
||||
onBeforeMount(async () => await setZxcvbnOptions(language.value))
|
||||
|
||||
function calculatePasswordStrength(password: string) {
|
||||
const zxcvbnResult = zxcvbn(password)
|
||||
passwordScore.value = zxcvbnResult.score
|
||||
passwordStrength.value = getPasswordStrength(passwordScore.value)
|
||||
passwordSuggestions.value = zxcvbnResult.feedback.suggestions
|
||||
backgroundSize.value = (passwordScore.value * 100) / 4 + '% 100%'
|
||||
}
|
||||
|
||||
watch(
|
||||
() => language.value,
|
||||
async (newLanguageValue) => {
|
||||
await setZxcvbnOptions(newLanguageValue)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => password.value,
|
||||
async (newPassword) => {
|
||||
if (isSuccess.value) {
|
||||
passwordStrength.value = ''
|
||||
} else {
|
||||
calculatePasswordStrength(newPassword)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.password-strength {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@mixin slider-background-image($color) {
|
||||
background: var(--password-bg-color);
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
20% 0%,
|
||||
20% 100%,
|
||||
color-stop(0%, $color),
|
||||
color-stop(100%, $color)
|
||||
);
|
||||
background-image: -webkit-linear-gradient(left, $color 0%, $color 100%);
|
||||
background-image: -moz-linear-gradient(left, $color 0%, $color 100%);
|
||||
background-image: -o-linear-gradient(to right, $color 0%, $color 100%);
|
||||
background-image: linear-gradient(to right, $color 0%, $color 100%);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.password-slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
height: 5px;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
.strength-0,
|
||||
.strength-1 {
|
||||
@include slider-background-image(var(--password-color-weak));
|
||||
}
|
||||
.strength-2 {
|
||||
@include slider-background-image(var(--password-color-medium));
|
||||
}
|
||||
.strength-3 {
|
||||
@include slider-background-image(var(--password-color-good));
|
||||
}
|
||||
.strength-4 {
|
||||
@include slider-background-image(var(--password-color-strong));
|
||||
}
|
||||
|
||||
.password-slider::-webkit-slider-thumb,
|
||||
.password-slider::-moz-range-thumb {
|
||||
opacity: 0;
|
||||
}
|
||||
.password-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.password-slider::-moz-range-thumb {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.password-strength-details {
|
||||
margin-bottom: $default-margin * 0.5;
|
||||
margin-top: -1 * $default-margin;
|
||||
padding: 0 $default-padding;
|
||||
|
||||
.password-strength-value {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.info-box {
|
||||
padding: $default-padding * 0.1 $default-padding;
|
||||
.password-feedback {
|
||||
padding-left: $default-padding * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -55,7 +55,7 @@
|
||||
function getSum(total: any, value: any): number {
|
||||
return getNumber(total) + getNumber(value)
|
||||
}
|
||||
let chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
||||
const chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
||||
labels: props.labels,
|
||||
// workaround to avoid dataset modification
|
||||
datasets: JSON.parse(JSON.stringify(props.datasets)),
|
||||
|
@ -96,7 +96,7 @@
|
||||
TStatisticsFromApi,
|
||||
IStatisticsParams,
|
||||
} from '@/types/statistics'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatStats } from '@/utils/statistics'
|
||||
|
||||
@ -111,7 +111,7 @@
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
type: Object as PropType<IAuthUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
chartParams: {
|
||||
@ -134,7 +134,7 @@
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
|
||||
let displayedData: Ref<TStatisticsDatasetKeys> = ref('total_distance')
|
||||
const displayedData: Ref<TStatisticsDatasetKeys> = ref('total_distance')
|
||||
const statistics: ComputedRef<TStatisticsFromApi> = computed(
|
||||
() => store.getters[STATS_STORE.GETTERS.USER_STATS]
|
||||
)
|
||||
@ -169,7 +169,7 @@
|
||||
}
|
||||
function getApiParams(
|
||||
chartParams: IStatisticsDateParams,
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
): IStatisticsParams {
|
||||
return {
|
||||
from: format(chartParams.start, 'yyyy-MM-dd'),
|
||||
|
@ -53,7 +53,7 @@
|
||||
const store = useStore()
|
||||
|
||||
const { sports, user } = toRefs(props)
|
||||
let page = ref(1)
|
||||
const page = ref(1)
|
||||
const per_page = 5
|
||||
const initWorkoutsCount =
|
||||
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
|
||||
|
@ -33,7 +33,7 @@
|
||||
import CalendarHeader from '@/components/Dashboard/UserCalendar/CalendarHeader.vue'
|
||||
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkout, TWorkoutsPayload } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getCalendarStartAndEnd } from '@/utils/dates'
|
||||
@ -41,7 +41,7 @@
|
||||
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@ -49,8 +49,8 @@
|
||||
|
||||
const { sports, user } = toRefs(props)
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
let day = ref(new Date())
|
||||
let calendarDates = ref(getCalendarStartAndEnd(day.value, props.user.weekm))
|
||||
const day = ref(new Date())
|
||||
const calendarDates = ref(getCalendarStartAndEnd(day.value, props.user.weekm))
|
||||
const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
|
||||
)
|
||||
|
@ -6,9 +6,13 @@
|
||||
{{ sportTranslatedLabel }}
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="record" v-for="record in records.records" :key="record.id">
|
||||
<div
|
||||
class="record"
|
||||
v-for="record in getTranslatedRecords(records.records)"
|
||||
:key="record.id"
|
||||
>
|
||||
<span class="record-type">
|
||||
{{ $t(`workouts.RECORD_${record.record_type}`) }}
|
||||
{{ record.label }}
|
||||
</span>
|
||||
<span class="record-value">{{ record.value }}</span>
|
||||
<span class="record-date">
|
||||
@ -29,8 +33,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { IRecordsBySports } from '@/types/workouts'
|
||||
import { ICardRecord, IRecord, IRecordsBySports } from '@/types/workouts'
|
||||
import { sortRecords } from '@/utils/records'
|
||||
|
||||
interface Props {
|
||||
records: IRecordsBySports
|
||||
@ -39,6 +45,19 @@
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { records, sportTranslatedLabel } = toRefs(props)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
function getTranslatedRecords(records: IRecord[]): ICardRecord[] {
|
||||
const translatedRecords: ICardRecord[] = []
|
||||
records.map((record) => {
|
||||
translatedRecords.push({
|
||||
...record,
|
||||
label: t(`workouts.RECORD_${record.record_type}`),
|
||||
})
|
||||
})
|
||||
return translatedRecords.sort(sortRecords)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -64,6 +83,7 @@
|
||||
padding: $default-padding;
|
||||
.record {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
span {
|
||||
padding: 2px 5px;
|
||||
@ -73,6 +93,7 @@
|
||||
}
|
||||
.record-value {
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
padding-right: $default-padding * 2;
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,13 @@
|
||||
|
||||
import RecordsCard from '@/components/Dashboard/UserRecords/RecordsCard.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { getRecordsBySports } from '@/utils/records'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
|
@ -34,10 +34,10 @@
|
||||
|
||||
import StatCard from '@/components/Common/StatCard.vue'
|
||||
import { TUnit } from '@/types/units'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { convertDistance, units } from '@/utils/units'
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@ -52,16 +52,19 @@
|
||||
const distanceUnitTo: TUnit = user.value.imperial_units
|
||||
? units[distanceUnitFrom].defaultTarget
|
||||
: distanceUnitFrom
|
||||
const totalDistance = user.value.imperial_units
|
||||
? convertDistance(user.value.total_distance, distanceUnitFrom, distanceUnitTo, 2)
|
||||
: parseFloat(user.value.total_distance.toFixed(2))
|
||||
const totalDistance: ComputedRef<number> = computed(() =>
|
||||
user.value.imperial_units
|
||||
? convertDistance(user.value.total_distance, distanceUnitFrom, distanceUnitTo, 2)
|
||||
: parseFloat(user.value.total_distance.toFixed(2)))
|
||||
const ascentUnitFrom: TUnit = 'm'
|
||||
const ascentUnitTo: TUnit = user.value.imperial_units
|
||||
? units[ascentUnitFrom].defaultTarget
|
||||
: ascentUnitFrom
|
||||
const totalAscent = user.value.imperial_units
|
||||
? convertDistance(user.value.total_ascent, ascentUnitFrom, ascentUnitTo, 2)
|
||||
: parseFloat(user.value.total_ascent.toFixed(2))
|
||||
const totalAscent: ComputedRef<number> = computed(() =>
|
||||
user.value.imperial_units
|
||||
? convertDistance(user.value.total_ascent, ascentUnitFrom, ascentUnitTo, 2)
|
||||
: parseFloat(user.value.total_ascent.toFixed(2)))
|
||||
|
||||
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
|
@ -7,21 +7,13 @@
|
||||
</div>
|
||||
<div class="footer-item bullet">•</div>
|
||||
<div class="footer-item">
|
||||
<a
|
||||
href="https://github.com/SamR1/FitTrackee"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
source code
|
||||
</a>
|
||||
under
|
||||
<a
|
||||
href="https://choosealicense.com/licenses/agpl-3.0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
AGPLv3 </a
|
||||
>license
|
||||
<router-link to="/about">
|
||||
{{ $t('common.ABOUT') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="footer-item bullet" v-if="adminContact">•</div>
|
||||
<div class="footer-item" v-if="adminContact">
|
||||
<a :href="`mailto:${adminContact}`">{{ $t('common.CONTACT') }}</a>
|
||||
</div>
|
||||
<div class="footer-item bullet">•</div>
|
||||
<div class="footer-item">
|
||||
@ -30,7 +22,7 @@
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
documentation
|
||||
{{ $t('common.DOCUMENTATION') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -42,10 +34,11 @@
|
||||
|
||||
interface Props {
|
||||
version: string
|
||||
adminContact?: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { version } = toRefs(props)
|
||||
const { adminContact, version } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -79,21 +79,19 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, capitalize } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { IDropdownOption } from '@/types/forms'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
const emit = defineEmits(['menuInteraction'])
|
||||
|
||||
const { locale } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
const authUser: ComputedRef<IAuthUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const isAuthenticated: ComputedRef<boolean> = computed(
|
||||
@ -102,7 +100,7 @@
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let isMenuOpen = ref(false)
|
||||
const isMenuOpen = ref(false)
|
||||
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
@ -113,8 +111,10 @@
|
||||
emit('menuInteraction', false)
|
||||
}
|
||||
function updateLanguage(option: IDropdownOption) {
|
||||
locale.value = option.value.toString()
|
||||
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
|
||||
store.dispatch(
|
||||
ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE,
|
||||
option.value.toString()
|
||||
)
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
|
@ -2,32 +2,17 @@
|
||||
<div id="no-config">
|
||||
<div class="error-page">
|
||||
<div class="error-img">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -51 512 512">
|
||||
<g id="error">
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 0 0 C 0 11.300781 0 399.777344 0 410 L 512 410 C 512 402.324219 512 2.425781 512 0 Z M 370 71 L 370 30 L 411 30 L 411 71 Z M 30 30 L 340 30 L 340 71 L 30 71 Z M 482 380 L 30 380 L 30 101 L 482 101 Z M 441 71 L 441 30 L 482 30 L 482 71 Z M 441 71 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 325.519531 297.070312 C 294.328125 265.878906 294.328125 215.125 325.519531 183.929688 L 304.304688 162.71875 C 261.417969 205.605469 261.417969 275.390625 304.304688 318.28125 Z M 325.519531 297.070312 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 197.089844 180 L 237.089844 180 L 237.089844 220 L 197.089844 220 Z M 197.089844 180 "
|
||||
/>
|
||||
<path
|
||||
class="error-page-img"
|
||||
d="M 197.089844 261 L 237.089844 261 L 237.089844 301 L 197.089844 301 Z M 197.089844 261 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<ErrorImg />
|
||||
</div>
|
||||
<p class="error-message" v-html="$t('error.APP_ERROR')" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ErrorImg from '@/components/Common/Images/ErrorImg.vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
@ -49,12 +34,10 @@
|
||||
width: 150px;
|
||||
|
||||
svg {
|
||||
.error-page-img {
|
||||
stroke: none;
|
||||
fill-rule: nonzero;
|
||||
fill: var(--app-color);
|
||||
filter: var(--svg-filter);
|
||||
}
|
||||
stroke: none;
|
||||
fill-rule: nonzero;
|
||||
fill: var(--app-color);
|
||||
filter: var(--svg-filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
|
||||
|
||||
let selectedTimeFrame = ref('month')
|
||||
const selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
|
||||
function onUpdateTimeFrame(timeFrame: string) {
|
||||
|
@ -41,7 +41,7 @@
|
||||
const { t } = useI18n()
|
||||
|
||||
const { sports, user } = toRefs(props)
|
||||
let selectedTimeFrame = ref('month')
|
||||
const selectedTimeFrame = ref('month')
|
||||
const chartParams: Ref<IStatisticsDateParams> = ref(
|
||||
getChartParams(selectedTimeFrame.value)
|
||||
)
|
||||
|
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div id="account-confirmation-email" class="center-card with-margin">
|
||||
<div class="email-sent" v-if="action === 'email-sent'">
|
||||
<EmailSent />
|
||||
<div class="email-sent-message">
|
||||
{{ $t('user.ACCOUNT_CONFIRMATION_SENT') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Card>
|
||||
<template #title>{{ $t('user.RESENT_ACCOUNT_CONFIRMATION') }}</template>
|
||||
<template #content>
|
||||
<UserAuthForm :action="action" />
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import EmailSent from '@/components/Common/Images/EmailSent.vue'
|
||||
import UserAuthForm from '@/components/User/UserAuthForm.vue'
|
||||
|
||||
interface Props {
|
||||
action: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { action } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
#account-confirmation-email {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.email-sent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
svg {
|
||||
stroke: none;
|
||||
fill-rule: nonzero;
|
||||
fill: var(--app-color);
|
||||
filter: var(--svg-filter);
|
||||
width: 100px;
|
||||
}
|
||||
.email-sent-message {
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep(.card) {
|
||||
.card-content {
|
||||
#user-auth-form {
|
||||
margin-top: 0;
|
||||
#user-form {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="password-action-done" class="center-card center-card with-margin">
|
||||
<div id="password-action-done" class="center-card with-margin">
|
||||
<EmailSent v-if="action === 'request-sent'" />
|
||||
<Password v-else />
|
||||
<div class="password-message">
|
||||
|
@ -16,10 +16,10 @@
|
||||
unitFrom="km"
|
||||
:digits="0"
|
||||
:displayUnit="false"
|
||||
:useImperialUnits="user.imperial_units"
|
||||
:useImperialUnits="authUser.imperial_units"
|
||||
/>
|
||||
<span class="stat-label">
|
||||
{{ user.imperial_units ? 'miles' : 'km' }}
|
||||
{{ authUser.imperial_units ? 'miles' : 'km' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="user-stat hide-small">
|
||||
@ -34,10 +34,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
import { computed, ComputedRef, toRefs } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IAuthUserProfile, IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
@ -45,6 +47,12 @@
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user } = toRefs(props)
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const authUser: ComputedRef<IAuthUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,52 +3,132 @@
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
message="admin.CONFIRM_USER_ACCOUNT_DELETION"
|
||||
:message="
|
||||
displayModal === 'delete'
|
||||
? 'admin.CONFIRM_USER_ACCOUNT_DELETION'
|
||||
: 'admin.CONFIRM_USER_PASSWORD_RESET'
|
||||
"
|
||||
:strongMessage="user.username"
|
||||
@confirmAction="deleteUserAccount(user.username)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@confirmAction="
|
||||
displayModal === 'delete'
|
||||
? deleteUserAccount(user.username)
|
||||
: resetUserPassword(user.username)
|
||||
"
|
||||
@cancelAction="updateDisplayModal('')"
|
||||
/>
|
||||
<dl>
|
||||
<dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
|
||||
<dd>{{ registrationDate }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.FIRST_NAME') }}:</dt>
|
||||
<dd>{{ user.first_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.LAST_NAME') }}:</dt>
|
||||
<dd>{{ user.last_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIRTH_DATE') }}:</dt>
|
||||
<dd>{{ birthDate }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.LOCATION') }}:</dt>
|
||||
<dd>{{ user.location }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIO') }}:</dt>
|
||||
<dd class="user-bio">
|
||||
{{ user.bio }}
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="profile-buttons" v-if="fromAdmin">
|
||||
<button
|
||||
class="danger"
|
||||
v-if="authUser.username !== user.username"
|
||||
@click.prevent="updateDisplayModal(true)"
|
||||
>
|
||||
{{ $t('admin.DELETE_USER') }}
|
||||
</button>
|
||||
<button @click="$router.go(-1)">{{ $t('buttons.BACK') }}</button>
|
||||
<div class="info-box success-message" v-if="isSuccess">
|
||||
{{
|
||||
$t(
|
||||
`admin.${
|
||||
currentAction === 'password-reset'
|
||||
? 'PASSWORD_RESET'
|
||||
: 'USER_EMAIL_UPDATE'
|
||||
}_SUCCESSFUL`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="profile-buttons" v-else>
|
||||
<button @click="$router.push('/profile/edit')">
|
||||
{{ $t('user.PROFILE.EDIT') }}
|
||||
</button>
|
||||
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
|
||||
<AlertMessage
|
||||
message="user.THIS_USER_ACCOUNT_IS_INACTIVE"
|
||||
v-if="!user.is_active"
|
||||
/>
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
<div class="email-form form-box" v-if="displayUserEmailForm">
|
||||
<form
|
||||
:class="{ errors: formErrors }"
|
||||
@submit.prevent="updateUserEmail(user.username)"
|
||||
>
|
||||
<label class="form-items" for="email">
|
||||
{{ $t('admin.CURRENT_EMAIL') }}
|
||||
<input id="email" type="email" v-model="user.email" disabled />
|
||||
</label>
|
||||
<label class="form-items" for="email">
|
||||
{{ $t('admin.NEW_EMAIL') }}*
|
||||
<input id="new-email" type="email" required v-model="newUserEmail" />
|
||||
</label>
|
||||
<div class="form-buttons">
|
||||
<button class="confirm" type="submit">
|
||||
{{ $t('buttons.SUBMIT') }}
|
||||
</button>
|
||||
<button class="cancel" @click.prevent="hideEmailForm">
|
||||
{{ $t('buttons.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
<dl>
|
||||
<dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
|
||||
<dd>{{ registrationDate }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.FIRST_NAME') }}:</dt>
|
||||
<dd>{{ user.first_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.LAST_NAME') }}:</dt>
|
||||
<dd>{{ user.last_name }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIRTH_DATE') }}:</dt>
|
||||
<dd>{{ birthDate }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.LOCATION') }}:</dt>
|
||||
<dd>{{ user.location }}</dd>
|
||||
<dt>{{ $t('user.PROFILE.BIO') }}:</dt>
|
||||
<dd class="user-bio">
|
||||
{{ user.bio }}
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="profile-buttons" v-if="fromAdmin">
|
||||
<button
|
||||
class="danger"
|
||||
v-if="authUser.username !== user.username"
|
||||
@click.prevent="updateDisplayModal('delete')"
|
||||
>
|
||||
{{ $t('admin.DELETE_USER') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!user.is_active"
|
||||
@click.prevent="confirmUserAccount(user.username)"
|
||||
>
|
||||
{{ $t('admin.ACTIVATE_USER_ACCOUNT') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="authUser.username !== user.username"
|
||||
@click.prevent="displayEmailForm"
|
||||
>
|
||||
{{ $t('admin.UPDATE_USER_EMAIL') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="
|
||||
authUser.username !== user.username &&
|
||||
appConfig.is_email_sending_enabled
|
||||
"
|
||||
@click.prevent="updateDisplayModal('reset')"
|
||||
>
|
||||
{{ $t('admin.RESET_USER_PASSWORD') }}
|
||||
</button>
|
||||
<button @click="$router.go(-1)">{{ $t('buttons.BACK') }}</button>
|
||||
</div>
|
||||
<div class="profile-buttons" v-else>
|
||||
<button @click="$router.push('/profile/edit')">
|
||||
{{ $t('user.PROFILE.EDIT') }}
|
||||
</button>
|
||||
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, withDefaults } from 'vue'
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
ref,
|
||||
toRefs,
|
||||
withDefaults,
|
||||
watch,
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
|
||||
import { AUTH_USER_STORE, USERS_STORE } from '@/store/constants'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { AUTH_USER_STORE, ROOT_STORE, USERS_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { IAuthUserProfile, IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
interface Props {
|
||||
@ -62,7 +142,7 @@
|
||||
const store = useStore()
|
||||
|
||||
const { user, fromAdmin } = toRefs(props)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
const authUser: ComputedRef<IAuthUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const registrationDate = computed(() =>
|
||||
@ -75,20 +155,106 @@
|
||||
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
|
||||
: ''
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
const isSuccess = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS_IS_SUCCESS]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const displayModal: Ref<string> = ref('')
|
||||
const formErrors = ref(false)
|
||||
const displayUserEmailForm: Ref<boolean> = ref(false)
|
||||
const newUserEmail: Ref<string> = ref('')
|
||||
const currentAction: Ref<string> = ref('')
|
||||
|
||||
function updateDisplayModal(value: boolean) {
|
||||
function updateDisplayModal(value: string) {
|
||||
displayModal.value = value
|
||||
if (value !== '') {
|
||||
store.commit(USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
}
|
||||
}
|
||||
function deleteUserAccount(username: string) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.DELETE_USER_ACCOUNT, { username })
|
||||
}
|
||||
function resetUserPassword(username: string) {
|
||||
currentAction.value = 'password-reset'
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
resetPassword: true,
|
||||
})
|
||||
}
|
||||
function confirmUserAccount(username: string) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
activate: true,
|
||||
})
|
||||
}
|
||||
function displayEmailForm() {
|
||||
resetErrorsAndSuccess()
|
||||
newUserEmail.value = user.value.email_to_confirm
|
||||
? user.value.email_to_confirm
|
||||
: ''
|
||||
displayUserEmailForm.value = true
|
||||
currentAction.value = 'email-update'
|
||||
}
|
||||
function hideEmailForm() {
|
||||
newUserEmail.value = ''
|
||||
displayUserEmailForm.value = false
|
||||
}
|
||||
function updateUserEmail(username: string) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
new_email: newUserEmail.value,
|
||||
})
|
||||
}
|
||||
function resetErrorsAndSuccess() {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
store.commit(USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
currentAction.value = ''
|
||||
}
|
||||
|
||||
onUnmounted(() => resetErrorsAndSuccess())
|
||||
|
||||
watch(
|
||||
() => isSuccess.value,
|
||||
(newIsSuccess) => {
|
||||
if (newIsSuccess) {
|
||||
updateDisplayModal('')
|
||||
hideEmailForm()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
#user-infos {
|
||||
.user-bio {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.email-form {
|
||||
display: flex;
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
gap: $default-padding;
|
||||
margin-top: $default-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div id="user-infos-edition">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('user.CONFIRM_ACCOUNT_DELETION')"
|
||||
@confirmAction="deleteAccount(user.username)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
/>
|
||||
<div class="profile-form form-box">
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
<div class="info-box success-message" v-if="isSuccess">
|
||||
{{
|
||||
$t(
|
||||
`user.PROFILE.SUCCESSFUL_${
|
||||
emailUpdate && appConfig.is_email_sending_enabled ? 'EMAIL_' : ''
|
||||
}UPDATE`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<form :class="{ errors: formErrors }" @submit.prevent="updateProfile">
|
||||
<label class="form-items" for="email">
|
||||
{{ $t('user.EMAIL') }}*
|
||||
<input
|
||||
id="email"
|
||||
v-model="userForm.email"
|
||||
:disabled="loading"
|
||||
:required="true"
|
||||
@invalid="invalidateForm"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items" for="password-field">
|
||||
{{ $t('user.CURRENT_PASSWORD') }}*
|
||||
<PasswordInput
|
||||
id="password-field"
|
||||
:disabled="loading"
|
||||
:password="userForm.password"
|
||||
:required="true"
|
||||
@updatePassword="updatePassword"
|
||||
@passwordError="invalidateForm"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items" for="new-password-field">
|
||||
{{ $t('user.NEW_PASSWORD') }}
|
||||
<PasswordInput
|
||||
id="new-password-field"
|
||||
:disabled="loading"
|
||||
:checkStrength="true"
|
||||
:password="userForm.new_password"
|
||||
:isSuccess="false"
|
||||
@updatePassword="updateNewPassword"
|
||||
@passwordError="invalidateForm"
|
||||
/>
|
||||
</label>
|
||||
<div class="form-buttons">
|
||||
<button class="confirm" type="submit">
|
||||
{{ $t('buttons.SUBMIT') }}
|
||||
</button>
|
||||
<button class="cancel" @click.prevent="$router.push('/profile')">
|
||||
{{ $t('buttons.CANCEL') }}
|
||||
</button>
|
||||
<button class="danger" @click.prevent="updateDisplayModal(true)">
|
||||
{{ $t('buttons.DELETE_MY_ACCOUNT') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
onMounted,
|
||||
watch,
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
|
||||
import PasswordInput from '@/components/Common/PasswordInput.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { IUserProfile, IUserAccountPayload } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { user } = toRefs(props)
|
||||
|
||||
const store = useStore()
|
||||
const userForm: IUserAccountPayload = reactive({
|
||||
email: '',
|
||||
password: '',
|
||||
new_password: '',
|
||||
})
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const isSuccess: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_SUCCESS]
|
||||
)
|
||||
const emailUpdate = ref(false)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const formErrors = ref(false)
|
||||
const displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function invalidateForm() {
|
||||
formErrors.value = true
|
||||
}
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.email = user.email
|
||||
}
|
||||
function updatePassword(password: string) {
|
||||
userForm.password = password
|
||||
}
|
||||
function updateNewPassword(new_password: string) {
|
||||
userForm.new_password = new_password
|
||||
}
|
||||
function updateProfile() {
|
||||
const payload: IUserAccountPayload = {
|
||||
email: userForm.email,
|
||||
password: userForm.password,
|
||||
}
|
||||
if (userForm.new_password) {
|
||||
payload.new_password = userForm.new_password
|
||||
}
|
||||
emailUpdate.value = userForm.email !== user.value.email
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_ACCOUNT, payload)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
store.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => isSuccess.value,
|
||||
async (isSuccessValue) => {
|
||||
if (isSuccessValue) {
|
||||
updatePassword('')
|
||||
updateNewPassword('')
|
||||
updateUserForm(user.value)
|
||||
formErrors.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => user.value.email,
|
||||
async () => {
|
||||
updateUserForm(user.value)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.form-items {
|
||||
.password-input {
|
||||
::v-deep(.show-password) {
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
margin-top: -4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
::v-deep(.form-info) {
|
||||
font-weight: normal;
|
||||
padding-left: $default-padding;
|
||||
}
|
||||
::v-deep(.password-strength-details) {
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
flex-direction: row;
|
||||
@media screen and (max-width: $x-small-limit) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,42 +1,12 @@
|
||||
<template>
|
||||
<div id="user-infos-edition">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('user.CONFIRM_ACCOUNT_DELETION')"
|
||||
@confirmAction="deleteAccount(user.username)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
/>
|
||||
<div class="profile-form form-box">
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
<form @submit.prevent="updateProfile">
|
||||
<label class="form-items" for="email">
|
||||
{{ $t('user.EMAIL') }}
|
||||
<input id="email" :value="user.email" disabled />
|
||||
</label>
|
||||
<label class="form-items" for="registrationDate">
|
||||
{{ $t('user.PROFILE.REGISTRATION_DATE') }}
|
||||
<input id="registrationDate" :value="registrationDate" disabled />
|
||||
</label>
|
||||
<label class="form-items" for="password">
|
||||
{{ $t('user.PASSWORD') }}
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
v-model="userForm.password"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items" for="passwordConfirmation">
|
||||
{{ $t('user.PASSWORD_CONFIRMATION') }}
|
||||
<input
|
||||
id="passwordConfirmation"
|
||||
type="password"
|
||||
v-model="userForm.password_conf"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<hr />
|
||||
<label class="form-items" for="first_name">
|
||||
{{ $t('user.PROFILE.FIRST_NAME') }}
|
||||
<input
|
||||
@ -84,9 +54,6 @@
|
||||
<button class="cancel" @click.prevent="$router.push('/profile')">
|
||||
{{ $t('buttons.CANCEL') }}
|
||||
</button>
|
||||
<button class="danger" @click.prevent="updateDisplayModal(true)">
|
||||
{{ $t('buttons.DELETE_MY_ACCOUNT') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -95,15 +62,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import { ComputedRef, computed, reactive, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { IUserProfile, IUserPayload } from '@/types/user'
|
||||
@ -116,10 +75,7 @@
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user } = toRefs(props)
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
@ -137,7 +93,6 @@
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
@ -160,17 +115,26 @@
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.form-items {
|
||||
.password-input {
|
||||
::v-deep(.show-password) {
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
margin-top: -4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
flex-direction: row;
|
||||
@media screen and (max-width: $x-small-limit) {
|
||||
|
@ -33,7 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, Ref, computed, ref, toRefs } from 'vue'
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, onUnmounted } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -59,7 +59,7 @@
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
let pictureFile: Ref<File | null> = ref(null)
|
||||
const pictureFile: Ref<File | null> = ref(null)
|
||||
|
||||
function deleteUserPicture() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
|
||||
@ -76,6 +76,10 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -85,6 +89,7 @@
|
||||
.user-picture-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: $default-margin;
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
|
@ -68,7 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, reactive, onMounted } from 'vue'
|
||||
import { ComputedRef, computed, reactive, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -134,4 +134,8 @@
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
})
|
||||
</script>
|
||||
|
@ -34,7 +34,7 @@
|
||||
const store = useStore()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES', 'SPORTS']
|
||||
const tabs = ['PROFILE', 'ACCOUNT', 'PICTURE', 'PREFERENCES', 'SPORTS']
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
|
@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div id="user-auth-form">
|
||||
<div
|
||||
id="user-auth-form"
|
||||
:class="`${
|
||||
['reset', 'reset-request'].includes(action) ? action : 'user-form'
|
||||
}`"
|
||||
>
|
||||
<div id="user-form">
|
||||
<div
|
||||
class="form-box"
|
||||
@ -11,6 +16,26 @@
|
||||
message="user.REGISTER_DISABLED"
|
||||
v-if="registration_disabled"
|
||||
/>
|
||||
<AlertMessage
|
||||
message="admin.EMAIL_SENDING_DISABLED"
|
||||
v-if="sendingEmailDisabled"
|
||||
/>
|
||||
<div
|
||||
class="info-box success-message"
|
||||
v-if="isSuccess || isRegistrationSuccess"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
`user.PROFILE.SUCCESSFUL_${
|
||||
isRegistrationSuccess
|
||||
? `REGISTRATION${
|
||||
appConfig.is_email_sending_enabled ? '_WITH_EMAIL' : ''
|
||||
}`
|
||||
: 'UPDATE'
|
||||
}`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<form
|
||||
:class="{ errors: formErrors }"
|
||||
@submit.prevent="onSubmit(action)"
|
||||
@ -23,57 +48,61 @@
|
||||
required
|
||||
pattern="[a-zA-Z0-9_]+"
|
||||
minlength="3"
|
||||
maxlength="12"
|
||||
maxlength="30"
|
||||
@invalid="invalidateForm"
|
||||
v-model="formData.username"
|
||||
:placeholder="$t('user.USERNAME')"
|
||||
/>
|
||||
<div v-if="action === 'register'" class="form-info">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" />
|
||||
{{ $t('user.USERNAME_INFO') }}
|
||||
</div>
|
||||
<input
|
||||
v-if="action !== 'reset'"
|
||||
id="email"
|
||||
:disabled="registration_disabled"
|
||||
:disabled="registration_disabled || sendingEmailDisabled"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
type="email"
|
||||
v-model="formData.email"
|
||||
:placeholder="
|
||||
action === 'reset-request'
|
||||
? $t('user.ENTER_EMAIL')
|
||||
: $t('user.EMAIL')
|
||||
"
|
||||
:placeholder="$t('user.EMAIL')"
|
||||
/>
|
||||
<input
|
||||
v-if="action !== 'reset-request'"
|
||||
id="password"
|
||||
<div
|
||||
v-if="
|
||||
[
|
||||
'reset-request',
|
||||
'register',
|
||||
'account-confirmation-resend',
|
||||
].includes(action)
|
||||
"
|
||||
class="form-info"
|
||||
>
|
||||
<i class="fa fa-info-circle" aria-hidden="true" />
|
||||
{{ $t('user.EMAIL_INFO') }}
|
||||
</div>
|
||||
<PasswordInput
|
||||
v-if="
|
||||
!['account-confirmation-resend', 'reset-request'].includes(
|
||||
action
|
||||
)
|
||||
"
|
||||
:disabled="registration_disabled"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
type="password"
|
||||
minlength="8"
|
||||
v-model="formData.password"
|
||||
:required="true"
|
||||
:placeholder="
|
||||
action === 'reset'
|
||||
? $t('user.ENTER_PASSWORD')
|
||||
: $t('user.PASSWORD')
|
||||
"
|
||||
/>
|
||||
<input
|
||||
v-if="['register', 'reset'].includes(action)"
|
||||
id="confirm-password"
|
||||
:disabled="registration_disabled"
|
||||
type="password"
|
||||
minlength="8"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
v-model="formData.password_conf"
|
||||
:placeholder="
|
||||
action === 'reset'
|
||||
? $t('user.ENTER_PASSWORD_CONFIRMATION')
|
||||
: $t('user.PASSWORD_CONFIRM')
|
||||
"
|
||||
:password="formData.password"
|
||||
:checkStrength="['reset', 'register'].includes(action)"
|
||||
@updatePassword="updatePassword"
|
||||
@passwordError="invalidateForm"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" :disabled="registration_disabled">
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="registration_disabled || sendingEmailDisabled"
|
||||
>
|
||||
{{ $t(buttonText) }}
|
||||
</button>
|
||||
</form>
|
||||
@ -81,8 +110,12 @@
|
||||
<router-link class="links" to="/register">
|
||||
{{ $t('user.REGISTER') }}
|
||||
</router-link>
|
||||
-
|
||||
<router-link class="links" to="/password-reset/request">
|
||||
<span v-if="appConfig.is_email_sending_enabled">-</span>
|
||||
<router-link
|
||||
v-if="appConfig.is_email_sending_enabled"
|
||||
class="links"
|
||||
to="/password-reset/request"
|
||||
>
|
||||
{{ $t('user.PASSWORD_FORGOTTEN') }}
|
||||
</router-link>
|
||||
</div>
|
||||
@ -92,6 +125,16 @@
|
||||
{{ $t('user.LOGIN') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
['login', 'register'].includes(action) &&
|
||||
appConfig.is_email_sending_enabled
|
||||
"
|
||||
>
|
||||
<router-link class="links" to="/account-confirmation/resend">
|
||||
{{ $t('user.ACCOUNT_CONFIRMATION_NOT_RECEIVED') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
</div>
|
||||
</div>
|
||||
@ -110,6 +153,7 @@
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import PasswordInput from '@/components/Common/PasswordInput.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { ILoginRegisterFormData } from '@/types/user'
|
||||
@ -131,7 +175,6 @@
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_conf: '',
|
||||
})
|
||||
const buttonText: ComputedRef<string> = computed(() =>
|
||||
getButtonText(props.action)
|
||||
@ -139,13 +182,27 @@
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const isRegistrationSuccess: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_REGISTRATION_SUCCESS]
|
||||
)
|
||||
const isSuccess: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_SUCCESS]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
const registration_disabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.action === 'register' && !appConfig.value.is_registration_enabled
|
||||
)
|
||||
const sendingEmailDisabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
['reset-request', 'account-confirmation-resend'].includes(props.action) &&
|
||||
!appConfig.value.is_email_sending_enabled
|
||||
)
|
||||
const formErrors = ref(false)
|
||||
|
||||
function getButtonText(action: string): string {
|
||||
@ -160,6 +217,9 @@
|
||||
function invalidateForm() {
|
||||
formErrors.value = true
|
||||
}
|
||||
function updatePassword(password: string) {
|
||||
formData.password = password
|
||||
}
|
||||
function onSubmit(actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'reset':
|
||||
@ -171,7 +231,6 @@
|
||||
}
|
||||
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
|
||||
password: formData.password,
|
||||
password_conf: formData.password_conf,
|
||||
token: props.token,
|
||||
})
|
||||
case 'reset-request':
|
||||
@ -181,7 +240,15 @@
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
case 'account-confirmation-resend':
|
||||
return store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.RESEND_ACCOUNT_CONFIRMATION_EMAIL,
|
||||
{
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
default:
|
||||
formData['language'] = language.value
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
|
||||
actionType,
|
||||
formData,
|
||||
@ -193,13 +260,17 @@
|
||||
formData.username = ''
|
||||
formData.email = ''
|
||||
formData.password = ''
|
||||
formData.password_conf = ''
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
store.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
store.commit(
|
||||
AUTH_USER_STORE.MUTATIONS.UPDATE_IS_REGISTRATION_SUCCESS,
|
||||
false
|
||||
)
|
||||
formErrors.value = false
|
||||
resetFormData()
|
||||
}
|
||||
@ -212,10 +283,6 @@
|
||||
|
||||
#user-auth-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin: $default-margin 0;
|
||||
height: 100%;
|
||||
|
||||
#user-form {
|
||||
width: 60%;
|
||||
@ -229,7 +296,6 @@
|
||||
font-style: italic;
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: $default-margin;
|
||||
border: solid 1px var(--app-color);
|
||||
@ -238,16 +304,23 @@
|
||||
border-color: var(--disabled-color);
|
||||
}
|
||||
}
|
||||
.success-message {
|
||||
margin: $default-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
height: auto;
|
||||
margin-bottom: 50px;
|
||||
|
||||
#user-form {
|
||||
margin-top: $default-margin;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-form {
|
||||
margin-top: 200px;
|
||||
@media screen and (max-width: $small-limit) {
|
||||
margin-top: $default-margin;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
const authUserPictureUrl = computed(() =>
|
||||
props.user.picture
|
||||
? `${getApiUrl()}users/${props.user.username}/picture`
|
||||
? `${getApiUrl()}users/${props.user.username}/picture?${Date.now()}`
|
||||
: ''
|
||||
)
|
||||
</script>
|
||||
|
@ -35,8 +35,9 @@
|
||||
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'ACCOUNT':
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
return `/profile/edit/${tab.toLocaleLowerCase()}`
|
||||
case 'PREFERENCES':
|
||||
case 'SPORTS':
|
||||
return `/profile${
|
||||
@ -52,7 +53,10 @@
|
||||
<style lang="scss">
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.profile-tabs {
|
||||
margin: $default-margin 0 $default-margin;
|
||||
.profile-tabs-checkboxes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: $default-margin * 0.5;
|
||||
}
|
||||
</style>
|
||||
|
101
fittrackee_client/src/components/Users/UsersNameFilter.vue
Normal file
101
fittrackee_client/src/components/Users/UsersNameFilter.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="users-filters">
|
||||
<div class="search-username">
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
v-model.trim="username"
|
||||
@keyup.enter="searchUsers"
|
||||
:placeholder="$t('user.FILTER_ON_USERNAME')"
|
||||
/>
|
||||
<i
|
||||
v-if="username !== ''"
|
||||
class="fa fa-times"
|
||||
aria-hidden="true"
|
||||
@click="resetFilter"
|
||||
/>
|
||||
</div>
|
||||
<i
|
||||
class="fa fa-search"
|
||||
:class="{ 'fa-disabled': username === '' }"
|
||||
aria-hidden="true"
|
||||
@click="searchUsers"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const username = ref(route.query.q ? route.query.q : '')
|
||||
|
||||
const emit = defineEmits(['filterOnUsername'])
|
||||
function searchUsers() {
|
||||
if (username.value !== '') {
|
||||
emit('filterOnUsername', username)
|
||||
}
|
||||
}
|
||||
function resetFilter() {
|
||||
username.value = ''
|
||||
emit('filterOnUsername', username.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
.users-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: $default-padding 0;
|
||||
gap: $default-padding;
|
||||
|
||||
.fa {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.fa-disabled {
|
||||
color: var(--disabled-color);
|
||||
}
|
||||
|
||||
.search-username {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $default-padding;
|
||||
|
||||
border: solid 1px var(--card-border-color);
|
||||
border-radius: $border-radius;
|
||||
color: var(--info-color);
|
||||
width: 45%;
|
||||
|
||||
input {
|
||||
border: none;
|
||||
height: 12px;
|
||||
width: 90%;
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
.fa-times {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
.users-filters {
|
||||
.search-username {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: $x-small-limit) {
|
||||
.users-filters {
|
||||
.search-username {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -58,7 +58,7 @@
|
||||
|
||||
import { htmlLegendPlugin } from '@/components/Workout/WorkoutDetail/WorkoutChart/legend'
|
||||
import { TUnit } from '@/types/units'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import {
|
||||
IWorkoutChartData,
|
||||
IWorkoutData,
|
||||
@ -68,7 +68,7 @@
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
authUser: IAuthUserProfile
|
||||
workoutData: IWorkoutData
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
@ -77,14 +77,14 @@
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
let beginElevationAtZero = ref(true)
|
||||
const displayDistance = ref(true)
|
||||
const beginElevationAtZero = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t, props.authUser.imperial_units)
|
||||
)
|
||||
const fromKmUnit = getUnitTo('km')
|
||||
const fromMUnit = getUnitTo('m')
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
const chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
|
@ -46,7 +46,7 @@
|
||||
import WorkoutMap from '@/components/Workout/WorkoutDetail/WorkoutMap/index.vue'
|
||||
import { WORKOUTS_STORE } from '@/store/constants'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import {
|
||||
IWorkout,
|
||||
IWorkoutData,
|
||||
@ -58,7 +58,7 @@
|
||||
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
authUser: IAuthUserProfile
|
||||
displaySegment: boolean
|
||||
sports: ISport[]
|
||||
workoutData: IWorkoutData
|
||||
@ -75,7 +75,7 @@
|
||||
const workout: ComputedRef<IWorkout> = computed(
|
||||
() => props.workoutData.workout
|
||||
)
|
||||
let segmentId: Ref<number | null> = ref(
|
||||
const segmentId: Ref<number | null> = ref(
|
||||
route.params.workoutId ? +route.params.segmentId : null
|
||||
)
|
||||
const segment: ComputedRef<IWorkoutSegment | null> = computed(() =>
|
||||
@ -83,7 +83,7 @@
|
||||
? workout.value.segments[+segmentId.value - 1]
|
||||
: null
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
const displayModal: Ref<boolean> = ref(false)
|
||||
const sport = computed(() =>
|
||||
props.sports
|
||||
? props.sports.find(
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
id="workout-edition"
|
||||
class="center-card center-card with-margin"
|
||||
class="center-card with-margin"
|
||||
:class="{ 'center-form': workout && workout.with_gpx }"
|
||||
>
|
||||
<Card>
|
||||
@ -137,6 +137,8 @@
|
||||
class="workout-duration"
|
||||
type="text"
|
||||
placeholder="HH"
|
||||
minlength="1"
|
||||
maxlength="2"
|
||||
pattern="^([0-1]?[0-9]|2[0-3])$"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
@ -150,6 +152,8 @@
|
||||
class="workout-duration"
|
||||
type="text"
|
||||
pattern="^([0-5][0-9])$"
|
||||
minlength="2"
|
||||
maxlength="2"
|
||||
placeholder="MM"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
@ -163,6 +167,8 @@
|
||||
class="workout-duration"
|
||||
type="text"
|
||||
pattern="^([0-5][0-9])$"
|
||||
minlength="2"
|
||||
maxlength="2"
|
||||
placeholder="SS"
|
||||
required
|
||||
@invalid="invalidateForm"
|
||||
@ -237,7 +243,7 @@
|
||||
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkout, IWorkoutForm } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
|
||||
@ -246,7 +252,7 @@
|
||||
import { convertDistance } from '@/utils/units'
|
||||
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
authUser: IAuthUserProfile
|
||||
sports: ISport[]
|
||||
isCreation?: boolean
|
||||
loading?: boolean
|
||||
@ -295,7 +301,7 @@
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
})
|
||||
let withGpx = ref(
|
||||
const withGpx = ref(
|
||||
props.workout.id ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
@ -415,12 +421,6 @@
|
||||
@import '~@/scss/vars.scss';
|
||||
|
||||
#workout-edition {
|
||||
@media screen and (max-width: $small-limit) {
|
||||
&.center-form {
|
||||
margin: 50px auto;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.card) {
|
||||
.card-title {
|
||||
text-align: center;
|
||||
@ -510,5 +510,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
margin-bottom: 0;
|
||||
&.center-form {
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
&.with-margin {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -165,12 +165,12 @@
|
||||
import { LocationQuery, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
import { units } from '@/utils/units'
|
||||
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
authUser: IAuthUserProfile
|
||||
sports: ISport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
@ -25,7 +25,7 @@
|
||||
:query="query"
|
||||
/>
|
||||
<table>
|
||||
<thead>
|
||||
<thead :class="{ smaller: 'de' === currentLanguage }">
|
||||
<tr>
|
||||
<th class="sport-col" />
|
||||
<th>{{ capitalize($t('workouts.WORKOUT', 1)) }}</th>
|
||||
@ -71,7 +71,7 @@
|
||||
class="fa fa-map-o"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ workout.title }}
|
||||
<span class="title">{{ workout.title }}</span>
|
||||
</router-link>
|
||||
<StaticMap
|
||||
v-if="workout.with_gpx && hoverWorkoutId === workout.id"
|
||||
@ -79,7 +79,7 @@
|
||||
:display-hover="true"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<td class="workout-date">
|
||||
<span class="cell-heading">
|
||||
{{ $t('workouts.DATE') }}
|
||||
</span>
|
||||
@ -179,10 +179,10 @@
|
||||
import Pagination from '@/components/Common/Pagination.vue'
|
||||
import StaticMap from '@/components/Common/StaticMap.vue'
|
||||
import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
|
||||
import { WORKOUTS_STORE } from '@/store/constants'
|
||||
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { IPagination } from '@/types/api'
|
||||
import { ITranslatedSport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkout, TWorkoutsPayload } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getQuery, sortList, workoutsPayloadKeys } from '@/utils/api'
|
||||
@ -192,7 +192,7 @@
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
sports: ITranslatedSport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
@ -214,6 +214,9 @@
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.WORKOUTS_PAGINATION]
|
||||
)
|
||||
const currentLanguage: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let query: TWorkoutsPayload = getWorkoutsQuery(route.query)
|
||||
const hoverWorkoutId: Ref<string | null> = ref(null)
|
||||
|
||||
@ -238,19 +241,24 @@
|
||||
}
|
||||
|
||||
function getWorkoutsQuery(newQuery: LocationQuery): TWorkoutsPayload {
|
||||
query = getQuery(newQuery, orderByList, defaultOrder.order_by, {
|
||||
defaultSort: defaultOrder.order,
|
||||
})
|
||||
const workoutQuery = getQuery(
|
||||
newQuery,
|
||||
orderByList,
|
||||
defaultOrder.order_by,
|
||||
{
|
||||
defaultSort: defaultOrder.order,
|
||||
}
|
||||
)
|
||||
Object.keys(newQuery)
|
||||
.filter((k) => workoutsPayloadKeys.includes(k))
|
||||
.map((k) => {
|
||||
if (typeof newQuery[k] === 'string') {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
query[k] = newQuery[k]
|
||||
workoutQuery[k] = newQuery[k]
|
||||
}
|
||||
})
|
||||
return query
|
||||
return workoutQuery
|
||||
}
|
||||
|
||||
function getConvertedPayload(payload: TWorkoutsPayload): TWorkoutsPayload {
|
||||
@ -258,7 +266,7 @@
|
||||
...payload,
|
||||
}
|
||||
Object.entries(convertedPayload).map((entry) => {
|
||||
if (entry[0].match('speed|distance')) {
|
||||
if (entry[0].match('speed|distance') && entry[1]) {
|
||||
convertedPayload[entry[0]] = convertDistance(+entry[1], 'mi', 'km')
|
||||
}
|
||||
})
|
||||
@ -287,7 +295,7 @@
|
||||
width: 100%;
|
||||
|
||||
.box {
|
||||
padding: $default-padding $default-padding * 2;
|
||||
padding: $default-padding $default-padding * 1.5;
|
||||
@media screen and (max-width: $small-limit) {
|
||||
&.empty-table {
|
||||
display: none;
|
||||
@ -318,14 +326,33 @@
|
||||
}
|
||||
|
||||
.workouts-table {
|
||||
.smaller {
|
||||
th {
|
||||
font-size: 0.95em;
|
||||
padding: $default-padding 0;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
td {
|
||||
text-align: right;
|
||||
}
|
||||
.sport-col {
|
||||
padding-right: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.workout-title {
|
||||
max-width: 90px;
|
||||
text-align: left;
|
||||
width: 100px;
|
||||
position: relative;
|
||||
.fa-map-o {
|
||||
font-size: 0.75em;
|
||||
padding-right: $default-padding * 0.5;
|
||||
}
|
||||
.nav-item {
|
||||
white-space: nowrap;
|
||||
.title {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
.static-map {
|
||||
display: none;
|
||||
@ -339,14 +366,27 @@
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
.workout-date {
|
||||
max-width: 60px;
|
||||
text-align: left;
|
||||
}
|
||||
@media screen and (max-width: $small-limit) {
|
||||
td,
|
||||
.workout-date,
|
||||
.workout-title {
|
||||
text-align: center;
|
||||
}
|
||||
.sport-col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: $default-padding;
|
||||
}
|
||||
.workout-date {
|
||||
max-width: initial;
|
||||
}
|
||||
.workout-title {
|
||||
max-width: initial;
|
||||
width: 100%;
|
||||
}
|
||||
.workout-title:hover .static-map {
|
||||
display: none;
|
||||
|
6
fittrackee_client/src/locales/de/about.json
Normal file
6
fittrackee_client/src/locales/de/about.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"CONTACT_ADMIN": "Kontaktiere den Administrator",
|
||||
"FITTRACKEE_DESCRIPTION": "<strong>FitTrackee</strong> ist ein selbst-gehosteter Outdoor-Aktivitäts-Tracker.",
|
||||
"FITTRACKEE_LICENSE": "unter {0} Lizenz",
|
||||
"SOURCE_CODE": "Quellkode"
|
||||
}
|
59
fittrackee_client/src/locales/de/administration.json
Normal file
59
fittrackee_client/src/locales/de/administration.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"ACTION": "Aktion",
|
||||
"ACTIVATE_USER_ACCOUNT": "Aktiviere Konto",
|
||||
"ACTIVE": "Aktiv",
|
||||
"ADMIN_RIGHTS_DELETE_USER_ACCOUNT": "Hinzufügen/Entfernen von Administratorrechten, Lösche Nutzerkonto.",
|
||||
"ADMIN": "Admin",
|
||||
"ADMINISTRATION": "Administration",
|
||||
"APPLICATION": "Anwendung",
|
||||
"APP_CONFIG": {
|
||||
"ADMIN_CONTACT": "Kontakt-E-Mail des Administrators",
|
||||
"MAX_USERS_LABEL": "Max. Anzahl aktiver Nutzer",
|
||||
"MAX_USERS_HELP": "Wenn 0, gibt es keine Registrierungslimitierung..",
|
||||
"MAX_FILES_IN_ZIP_LABEL": "Max. Dateianzahl im zip Archiv",
|
||||
"NO_CONTACT_EMAIL": "keine Kontakt-E-Mail",
|
||||
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Max. Größe der hochgeladenen Dateien (in Mb)",
|
||||
"TITLE": "Anwendungskonfiguration",
|
||||
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. Größe des zip Archives (in Mb)"
|
||||
},
|
||||
"BACK_TO_ADMIN": "Zurück zu Admin",
|
||||
"CONFIRM_USER_ACCOUNT_DELETION": "Möchtest du wirklich das {0} Konto löschen? Alle Daten werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden.",
|
||||
"CONFIRM_USER_PASSWORD_RESET": "Möchtest du wirklich das {0} Passwort zurücksetzen?",
|
||||
"CURRENT_EMAIL": "Aktuelle E-Mail",
|
||||
"DELETE_USER": "Lösche Nutzer",
|
||||
"EMAIL_SENDING_DISABLED": "E-Mail-Versand ist deaktiviert.",
|
||||
"ENABLE_DISABLE_SPORTS": "Aktivieren/Deaktivieren von Sportarten.",
|
||||
"NEW_EMAIL": "Neue E-Mail",
|
||||
"PASSWORD_RESET_SUCCESSFUL": "Das wasswort wurde zurückgesetzt.",
|
||||
"REGISTRATION_DISABLED": "Registrierung ist derzeit deaktiviert.",
|
||||
"REGISTRATION_ENABLED": "Registrierung ist derzeit aktiviert.",
|
||||
"RESET_USER_PASSWORD": "Passwort zurücksetzen",
|
||||
"SPORTS": {
|
||||
"TABLE": {
|
||||
"ACTIVE": "Aktiv",
|
||||
"HAS_WORKOUTS": "Trainings existieren",
|
||||
"IMAGE": "Bild",
|
||||
"LABEL": "Titel"
|
||||
},
|
||||
"TITLE": "Sportarten Administration"
|
||||
},
|
||||
"UPDATE_APPLICATION_DESCRIPTION": "Aktualisiere Anwemdungskonfiguration (maximale Anzahl an registrierten Nutzern, maximale Dateigröße).",
|
||||
"UPDATE_USER_EMAIL": "Aktualisiere E-Mail",
|
||||
"USER": "Nutzer",
|
||||
"USER_EMAIL_UPDATE_SUCCESSFUL": "Die E-Mail Adresse wurde aktualisiert.",
|
||||
"USERS": {
|
||||
"TABLE": {
|
||||
"ADD_ADMIN_RIGHTS": "Administratorrechte hinzufügen",
|
||||
"REMOVE_ADMIN_RIGHTS": "Administratorrechte entfernen"
|
||||
},
|
||||
"SELECTS": {
|
||||
"ORDER_BY": {
|
||||
"ADMIN": "Adminstatus",
|
||||
"CREATED_AT": "Registrierungsdatum",
|
||||
"IS_ACTIVE": "Accountstatus",
|
||||
"USERNAME": "Nutzername",
|
||||
"WORKOUTS_COUNT": "Trainingsanzahl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
fittrackee_client/src/locales/de/api.json
Normal file
34
fittrackee_client/src/locales/de/api.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Fehler. Bitte versuche es erneut oder kontaktiere den Administrator.",
|
||||
"email: valid email must be provided": "E-Mail: Eine gültige E-Mail muss angegeben werden.",
|
||||
"error on getting configuration": "Fehler beim Abrufen der Konfiguration.",
|
||||
"error when updating configuration": "Fehler beim Aktualisieren der Konfiguration.",
|
||||
"error, please try again or contact the administrator": "Fehler. Bitte versuche es erneut oder kontaktiere den Administrator.",
|
||||
"error, registration is disabled": "Fehler. Die Registrierung ist deaktiviert.",
|
||||
"file extension not allowed": "Dateierweiterung ist nicht erlaubt.",
|
||||
"file size is greater than the allowed size": "Die Datei ist größer als erlaubt.",
|
||||
"invalid credentials": "Ungültige Anmeldedaten.",
|
||||
"invalid payload": "Die bereitgestellten Daten sind ungültig.",
|
||||
"invalid token, please log in again": "Ungültiges Token, bitte erneut anmelden.",
|
||||
"invalid token, please request a new token": "Ungültiges Token, bitte erneut anmelden.",
|
||||
"Network Error": "Netzwerkfehler.",
|
||||
"new email must be different than curent email": "Die neue E-Mail muss sich von der aktuellen E-Mail unterscheiden.",
|
||||
"no file part": "Keine Datei angegeben.",
|
||||
"no selected file": "Keine Datei ausgewählt.",
|
||||
"password: password and password confirmation do not match": "Passwort: Passwort und Passwortbestätigung stimmen nicht überein.",
|
||||
"provide a valid auth token": "Gebe ein gültiges Authentifizierungstoken an.",
|
||||
"sorry, that username is already taken": "Es tut mir leid, der Benutzername ist schon vergeben.",
|
||||
"sport does not exist": "Sportart existiert nicht.",
|
||||
"signature expired, please log in again": "Die Signatur ist abgelaufen. Bitte melde dich erneut an.",
|
||||
"successfully registered": "Registrierung erfolgreich.",
|
||||
"user does not exist": "Der Nutzer existiert nicht.",
|
||||
"valid email must be provided for admin contact": "Um den Administrator zu kontaktieren, muss eine gültige E-Mail-Adresse angegeben werden.",
|
||||
"you can not delete your account, no other user has admin rights": "Du kannst Dein Konto nicht löschen, da kein anderer Nutzer hat Administratorrechte besitzt.",
|
||||
"you do not have permissions": "Du hast keine Berechtigung."
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PREVIOUS": "Vorhergehende",
|
||||
"NEXT": "Nächste"
|
||||
}
|
||||
}
|
17
fittrackee_client/src/locales/de/buttons.json
Normal file
17
fittrackee_client/src/locales/de/buttons.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"ACCOUNT-CONFIRMATION-RESEND": "Bestätigungs-E-Mail erneut senden",
|
||||
"BACK": "Zurück",
|
||||
"CANCEL": "Abbrechen",
|
||||
"CLEAR_FILTER": "Filter löschen",
|
||||
"DELETE_MY_ACCOUNT": "Lösche meinen Account",
|
||||
"DISABLE": "Deaktivieren",
|
||||
"EDIT": "Editieren",
|
||||
"ENABLE": "Aktivieren",
|
||||
"FILTER": "Filter",
|
||||
"LOGIN": "Einloggen",
|
||||
"NO": "Nein",
|
||||
"REGISTER": "Registrieren",
|
||||
"RESET": "Zurücksetzen",
|
||||
"SUBMIT": "Speichern",
|
||||
"YES": "Ja"
|
||||
}
|
23
fittrackee_client/src/locales/de/common.json
Normal file
23
fittrackee_client/src/locales/de/common.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"ABOUT": "Über",
|
||||
"CONFIRMATION": "Bestätigung",
|
||||
"CONTACT": "Kontakt",
|
||||
"DAY": "Tag | Tage",
|
||||
"DOCUMENTATION": "Dokumentation (en)",
|
||||
"HOME": "Startseite",
|
||||
"HERE": "hier",
|
||||
"SELECTS": {
|
||||
"ORDER_BY": {
|
||||
"LABEL": "sortiert nach"
|
||||
},
|
||||
"ORDER": {
|
||||
"LABEL": "sortieren",
|
||||
"ASC": "aufsteigend",
|
||||
"DESC": "absteigend"
|
||||
},
|
||||
"PER_PAGE": {
|
||||
"LABEL": "pro Seite"
|
||||
}
|
||||
},
|
||||
"TOTAL": "Insgesamt"
|
||||
}
|
4
fittrackee_client/src/locales/de/dashboard.json
Normal file
4
fittrackee_client/src/locales/de/dashboard.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"DASHBOARD": "Dashboard",
|
||||
"THIS_MONTH": "Dieser Monat"
|
||||
}
|
25
fittrackee_client/src/locales/de/de.ts
Normal file
25
fittrackee_client/src/locales/de/de.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import AboutTranslations from './about.json'
|
||||
import AdministrationTranslations from './administration.json'
|
||||
import ApiTranslations from './api.json'
|
||||
import ButtonsTranslations from './buttons.json'
|
||||
import CommonTranslations from './common.json'
|
||||
import DashboardTranslations from './dashboard.json'
|
||||
import ErrorTranslations from './error.json'
|
||||
import SportsTranslations from './sports.json'
|
||||
import StatisticsTranslations from './statistics.json'
|
||||
import UserTranslations from './user.json'
|
||||
import WorkoutsTranslations from './workouts.json'
|
||||
|
||||
export default {
|
||||
about: AboutTranslations,
|
||||
admin: AdministrationTranslations,
|
||||
api: ApiTranslations,
|
||||
buttons: ButtonsTranslations,
|
||||
common: CommonTranslations,
|
||||
dashboard: DashboardTranslations,
|
||||
error: ErrorTranslations,
|
||||
sports: SportsTranslations,
|
||||
statistics: StatisticsTranslations,
|
||||
user: UserTranslations,
|
||||
workouts: WorkoutsTranslations,
|
||||
}
|
9
fittrackee_client/src/locales/de/error.json
Normal file
9
fittrackee_client/src/locales/de/error.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"UNKNOWN": "Fehler. Bitte versuche es erneut oder kontaktiere den Administrator.",
|
||||
"APP_ERROR": "Bei der Anwendung scheinen einige Probleme aufgetreten zu sein.<br />Bitte versuche es später noch einmal oder kontaktiere den Administrator.",
|
||||
"NOT_FOUND": {
|
||||
"PAGE": "Seite nicht gefunden",
|
||||
"WORKOUT": "Training nicht gefunden"
|
||||
},
|
||||
"SOMETHING_WRONG": "Etwas lief schief"
|
||||
}
|
38
fittrackee_client/src/locales/de/sports.json
Normal file
38
fittrackee_client/src/locales/de/sports.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"Cycling (Sport)": {
|
||||
"LABEL": "Radfahren (Sport)"
|
||||
},
|
||||
"Cycling (Transport)": {
|
||||
"LABEL": "Radfahren (Pendeln)"
|
||||
},
|
||||
"Hiking": {
|
||||
"LABEL": "Wandern"
|
||||
},
|
||||
"Mountain Biking": {
|
||||
"LABEL": "Mountainbiken"
|
||||
},
|
||||
"Mountain Biking (Electric)": {
|
||||
"LABEL": "Mountainbiken (elektrisch)"
|
||||
},
|
||||
"Rowing": {
|
||||
"LABEL": "Rudern"
|
||||
},
|
||||
"Running": {
|
||||
"LABEL": "Laufen"
|
||||
},
|
||||
"Skiing (Alpine)": {
|
||||
"LABEL": "Skifahren (Alpin)"
|
||||
},
|
||||
"Skiing (Cross Country)": {
|
||||
"LABEL": "Skifahren (Langlauf)"
|
||||
},
|
||||
"Snowshoes": {
|
||||
"LABEL": "Schneeschuhe"
|
||||
},
|
||||
"Trail": {
|
||||
"LABEL": "Trail"
|
||||
},
|
||||
"Walking": {
|
||||
"LABEL": "Walking"
|
||||
}
|
||||
}
|
8
fittrackee_client/src/locales/de/statistics.json
Normal file
8
fittrackee_client/src/locales/de/statistics.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"STATISTICS": "Statistik",
|
||||
"TIME_FRAMES": {
|
||||
"week": "Woche",
|
||||
"month": "Monat",
|
||||
"year": "Jahr"
|
||||
}
|
||||
}
|
108
fittrackee_client/src/locales/de/user.json
Normal file
108
fittrackee_client/src/locales/de/user.json
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"ACCOUNT_CONFIRMATION_NOT_RECEIVED": "Hast du keine Anweisungen erhalten?",
|
||||
"ACCOUNT_CONFIRMATION_SENT": "Prüfe Deine E-Mail. Eine neue Bestätigungsemail wurde an die angegebene Adresse geschickt.",
|
||||
"ADMIN": "Admin",
|
||||
"ALREADY_HAVE_ACCOUNT": "Hast du bereits ein Konto?",
|
||||
"CONFIRM_ACCOUNT_DELETION": "Möchtest Du Dein Konto wirklich löschen? Alle Daten werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden.",
|
||||
"CURRENT_PASSWORD": "Aktuelles Passwort",
|
||||
"EMAIL": "E-Mail",
|
||||
"EMAIL_INFO": "Gebe eine gültige E-Mail-Adresse an.",
|
||||
"ENTER_PASSWORD": "Gebe ein Passwort ein",
|
||||
"FILTER_ON_USERNAME": "Nach Benutzernamen filtern",
|
||||
"HIDE_PASSWORD": "Passwort verbergen",
|
||||
"INVALID_TOKEN": "Ungültiges Token, bitte fordere ein neues Passworts an.",
|
||||
"LANGUAGE": "Sprache",
|
||||
"LOG_IN": "Anmelden",
|
||||
"LOGIN": "Anmeldung",
|
||||
"LOGOUT": "Abmelden",
|
||||
"NEW_PASSWORD": "Neues Passwort",
|
||||
"NO_USERS_FOUND": "Keine Nutzer gefunden.",
|
||||
"PASSWORD": "Passwort",
|
||||
"PASSWORD_INFO": "Mindestens 8 Zeichen sind erforderlich.",
|
||||
"PASSWORD_FORGOTTEN": "Passwort vergessen?",
|
||||
"PASSWORD_RESET": "Passwort zurücksetzen",
|
||||
"PASSWORD_SENT_EMAIL_TEXT": "Prüfe Deine E-Mail. Wenn Deine Adresse in der Datenbank enthalten ist, wirst du eine E-Mail mit einem Link erhalten um Dein Passwort zurückzusetzen.",
|
||||
"PASSWORD_STRENGTH": {
|
||||
"WEAK": "schwach",
|
||||
"AVERAGE": "mittel",
|
||||
"GOOD": "gut",
|
||||
"STRONG": "start",
|
||||
"LABEL": "Passwortstärke",
|
||||
"SUGGESTIONS": {
|
||||
"l33t": "Vermeide vorhersehbare Buchstabenersetzungen wie {'@'} für a.",
|
||||
"reverseWords": "Vermeide umgekehrte Schreibweisen gebräuchlicher Wörter.",
|
||||
"allUppercase": "Schreibe einige, aber nicht alle Buchstaben groß.",
|
||||
"capitalization": "Schreibe mehr als nur den ersten Buchstaben groß.",
|
||||
"dates": "Vermeide Daten und Jahreszahlen, die mit Dir in Verbindung gebracht werden.",
|
||||
"recentYears": "Vermeide Angabe von letzten Jahreszahlen.",
|
||||
"associatedYears": "Vermeide Jahreszahlen, die mit Dir in Verbindung gebracht werden.",
|
||||
"sequences": "Vermeide gebräuchliche Zeichenfolgen.",
|
||||
"repeated": "Vermeide wiederholungen von Wörtern und Zeichen.",
|
||||
"longerKeyboardPattern": "Verwende längere Tastaturmuster und ändere mehrmals die Schreibrichtung.",
|
||||
"anotherWord": "Füge weitere weniger gebräuchliche Wörter hinzu.",
|
||||
"useWords": "Verwenden Sie mehrere Wörter, aber vermeide gebräuchliche Ausdrücke.",
|
||||
"noNeed": "Du kannst sichere Passwörter erstellen, ohne Symbole, Zahlen oder Großbuchstaben zu verwenden.",
|
||||
"pwned": "Wenn Sie dieses Passwort auch anderweitig verwenden, sollten Sie es ändern."
|
||||
}
|
||||
},
|
||||
"PASSWORD_UPDATED": "Dein Passwort wurde aktualisiert. Klicke {0} um dich anzumelden.",
|
||||
"PROFILE": {
|
||||
"ACCOUNT_EDITION": "Kontoausgabe",
|
||||
"BACK_TO_PROFILE": "Zurück zum Profil",
|
||||
"BIO": "Biographie",
|
||||
"BIRTH_DATE": "Geburtsdatum",
|
||||
"EDIT": "Profil bearbeiten",
|
||||
"EDIT_PREFERENCES": "Einstellungen ändern",
|
||||
"EDIT_SPORTS_PREFERENCES": "Einstellungen für Sportarten ändern",
|
||||
"ERRORED_EMAIL_UPDATE": "Bitte {0} um Deine E-Mail Adresse nochmals zu ändern oder kontaktiere den Administrator",
|
||||
"FIRST_NAME": "Vorname",
|
||||
"FIRST_DAY_OF_WEEK": "Erster Tag der Woche",
|
||||
"LANGUAGE": "Sprache",
|
||||
"LAST_NAME": "Nachname",
|
||||
"LOCATION": "Ort",
|
||||
"MONDAY": "Montag",
|
||||
"PICTURE": "Bild",
|
||||
"PICTURE_EDITION": "Bildausgabe",
|
||||
"PICTURE_UPDATE": "Bild aktualisieren",
|
||||
"PICTURE_REMOVE": "Bild entfernen",
|
||||
"PREFERENCES_EDITION": "Einstellungsausgabe",
|
||||
"PROFILE_EDITION": "Profil-Ausgabe",
|
||||
"REGISTRATION_DATE": "Regirierungsdatum",
|
||||
"SPORTS_EDITION": "Sportarten-Einstellungsausgabe",
|
||||
"SUNDAY": "Sontag",
|
||||
"TABS": {
|
||||
"ACCOUNT": "Konto",
|
||||
"PICTURE": "Bild",
|
||||
"PREFERENCES": "Einstellungen",
|
||||
"PROFILE": "Profil",
|
||||
"SPORTS": "Sportarten"
|
||||
},
|
||||
"SPORT": {
|
||||
"ACTION": "Aktion",
|
||||
"COLOR": "Farbe",
|
||||
"DISABLED_BY_ADMIN": "vom Admin deaktiviert",
|
||||
"IS_ACTIVE": "aktiv",
|
||||
"LABEL": "Titel",
|
||||
"STOPPED_SPEED_THRESHOLD": "Geschwindigkeitsschwellenwert für Stopp"
|
||||
},
|
||||
"SUCCESSFUL_EMAIL_UPDATE": "Dein Konto wurde erfolgreich aktualisiert. Bitte prüfe Deine E-Mail um die neue E-Mail Adresse zu bestätigen.",
|
||||
"SUCCESSFUL_REGISTRATION": "Dein Konto wurde erfolgreich erstellt.",
|
||||
"SUCCESSFUL_REGISTRATION_WITH_EMAIL": "Ein Aktivierungslink für Dein Konto wurde an die angegebene E-Mail Adresse geschickt.",
|
||||
"SUCCESSFUL_UPDATE": "Dein Konto wurde erfolgreich aktualisiert.",
|
||||
"UNITS": {
|
||||
"LABEL": "Einheiten für die Distanz",
|
||||
"IMPERIAL": "Imperiales System (ft, mi)",
|
||||
"METRIC": "Metrisches System (m, km)"
|
||||
},
|
||||
"TIMEZONE": "Zeitzone"
|
||||
},
|
||||
"REGISTER": "Registrieren",
|
||||
"RESENT_ACCOUNT_CONFIRMATION": "Sende Email zur Kontobestätigung erneut",
|
||||
"REGISTER_DISABLED": "Entschuldigung, die Registrierung ist deaktiviert.",
|
||||
"RESET_PASSWORD": "Passwort zurücksetzen",
|
||||
"SHOW_PASSWORD": "Passwort anzeigen",
|
||||
"THIS_USER_ACCOUNT_IS_INACTIVE": "Dieser Account ist inaktiv.",
|
||||
"USER_PICTURE": "Benutzerbild",
|
||||
"USERNAME": "Nutzername",
|
||||
"USERNAME_INFO": "3 bis 30 Zeichen sind erforderlich, nur alphanumerische Zeichen und der Unterstrich \"_\" sind erlaubt."
|
||||
}
|
101
fittrackee_client/src/locales/de/workouts.json
Normal file
101
fittrackee_client/src/locales/de/workouts.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"ADD_WORKOUT": "Training hinzufügen",
|
||||
"ANALYSIS": "Analyse",
|
||||
"ASCENT": "Aufstieg",
|
||||
"AVE_SPEED": "Durchschn. Geschwindigkeit",
|
||||
"AVERAGE_SPEED": "Durchschnittsgeschwindigkeit",
|
||||
"BACK_TO_WORKOUT": "zurück zum Training",
|
||||
"DATE": "Datum",
|
||||
"DESCENT": "Abstieg",
|
||||
"DISPLAY_FILTERS": "zeige Filter",
|
||||
"DISTANCE": "Entfernung",
|
||||
"DURATION": "Dauer",
|
||||
"EDIT_WORKOUT": "Training bearbeiten",
|
||||
"ELEVATION": "Höhe",
|
||||
"END": "Ende",
|
||||
"FROM": "Von",
|
||||
"GPX_FILE": ".gpx Datei",
|
||||
"HIDE_FILTERS": "verberge Filter",
|
||||
"LATEST_WORKOUTS": "Letzte Trainings",
|
||||
"LOAD_MORE_WORKOUT": "Lade mehr Trainings",
|
||||
"MAX_ALTITUDE": "maximale Höhe",
|
||||
"MAX_FILES": "Maximale Dateianzahl",
|
||||
"MAX_SIZE": "Maximalgröße",
|
||||
"MAX_SPEED": "Max. Geschwindigkeit",
|
||||
"MIN_ALTITUDE": "minimale Höhe",
|
||||
"NEXT_SEGMENT": "Nächstes Segment",
|
||||
"NEXT_WORKOUT": "Nächstes Training",
|
||||
"NO_DATA_CLEANING": "Daten aus gpx, ohne Bereinigung",
|
||||
"NO_FILE_PROVIDED": "Keine Datei angegeben",
|
||||
"NO_FOLDER": "enthält keinen Ordner",
|
||||
"NO_MAP": "Keine Karte",
|
||||
"NO_NEXT_SEGMENT": "Kein nächstes Segment",
|
||||
"NO_NEXT_WORKOUT": "Kein nächstes Training",
|
||||
"NO_NOTES": "Keine Anmerkungen",
|
||||
"NO_PREVIOUS_SEGMENT": "Kein vorheriges Segment",
|
||||
"NO_PREVIOUS_WORKOUT": "Kein vorheriges Training",
|
||||
"NO_RECORDS": "Keine Aufzeichnungen.",
|
||||
"NO_WORKOUTS": "Keine Trainings.",
|
||||
"NOTES": "Anmerkungen",
|
||||
"PAUSES": "Pausen",
|
||||
"PREVIOUS_SEGMENT": "Vorheriges Segment",
|
||||
"PREVIOUS_WORKOUT": "Vorheriges Training",
|
||||
"RECORD": "Aufzeichnung | Aufzeichnungen",
|
||||
"RECORD_AS": "Durchschn. Geschwindigkeit",
|
||||
"RECORD_FD": "Weiteste Entfernung",
|
||||
"RECORD_LD": "Längste Dauer",
|
||||
"RECORD_MS": "Max. Geschwindigkeit",
|
||||
"REMAINING_CHARS": "remaining characters",
|
||||
"SEGMENT": "Segment | Segmente",
|
||||
"SPEED": "Geschwindigkeit",
|
||||
"SPORT": "Sportart | Sportarten",
|
||||
"START": "Start",
|
||||
"START_AND_FINISH": "Start und Ziel",
|
||||
"START_ELEVATION_AT_ZERO": "Höhenachse bei Null starten",
|
||||
"TITLE": "Titel",
|
||||
"TO": "bis",
|
||||
"TOTAL_DURATION": "Gesamtdauer",
|
||||
"UPLOAD_FIRST_WORKOUT": "Füge erstes Training hinzu!",
|
||||
"WEATHER": {
|
||||
"HUMIDITY": "Luftfeuchtigkeit",
|
||||
"TEMPERATURE": "Temperatur",
|
||||
"WIND": "Wind",
|
||||
"WIND_DIRECTIONS": {
|
||||
"N": "N",
|
||||
"NNE": "NNO",
|
||||
"NE": "NO",
|
||||
"ENE": "ONO",
|
||||
"E": "O",
|
||||
"ESE": "OSO",
|
||||
"SE": "SO",
|
||||
"SSE": "SSO",
|
||||
"S": "S",
|
||||
"SSW": "SSW",
|
||||
"SW": "SW",
|
||||
"WSW": "WSW",
|
||||
"W": "W",
|
||||
"WNW": "WNW",
|
||||
"NW": "NW",
|
||||
"NNW": "NNW"
|
||||
},
|
||||
"DARK_SKY": {
|
||||
"clear-day": "klarer Tag",
|
||||
"clear-night": "klare Nacht",
|
||||
"cloudy": "wolkig",
|
||||
"fog": "Nebel",
|
||||
"partly-cloudy-day": "teilweise bewölkter Tag",
|
||||
"partly-cloudy-night": "teilweise bewölkte Nacht",
|
||||
"rain": "Regen",
|
||||
"sleet": "Schneeregen",
|
||||
"snow": "Schnee",
|
||||
"wind": "Wind"
|
||||
}
|
||||
},
|
||||
"WITH_GPX": "mit .gpx Datei",
|
||||
"WITHOUT_GPX": "ohne .gpx Datei",
|
||||
"WORKOUT": "Training | Trainings",
|
||||
"WORKOUT_DATE": "Trainingsdatum",
|
||||
"WORKOUT_DELETION_CONFIRMATION": "Bist du sicher, dass du dieses Training löschen möchtest?",
|
||||
"ZIP_ARCHIVE": ".zip Datei",
|
||||
"ZIP_ARCHIVE_DESCRIPTION": "oder .zip Datei mit .gpx Dateien"
|
||||
}
|
6
fittrackee_client/src/locales/en/about.json
Normal file
6
fittrackee_client/src/locales/en/about.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"CONTACT_ADMIN": "Contact the administrator",
|
||||
"FITTRACKEE_DESCRIPTION": "<strong>FitTrackee</strong> is a self-hosted outdoor activity tracker.",
|
||||
"FITTRACKEE_LICENSE": "under {0} license ",
|
||||
"SOURCE_CODE": "Source code"
|
||||
}
|
@ -1,23 +1,33 @@
|
||||
{
|
||||
"ACTION": "Action",
|
||||
"ACTIVATE_USER_ACCOUNT": "Activate account",
|
||||
"ACTIVE": "Active",
|
||||
"ADMIN_RIGHTS_DELETE_USER_ACCOUNT": "Add/remove admin rights, delete user account.",
|
||||
"ADMIN": "Admin",
|
||||
"ADMINISTRATION": "Administration",
|
||||
"APPLICATION": "Application",
|
||||
"APP_CONFIG": {
|
||||
"ADMIN_CONTACT": "Administrator email for contact",
|
||||
"MAX_USERS_LABEL": "Max. number of active users",
|
||||
"MAX_USERS_HELP": "If 0, no limitation on registration.",
|
||||
"MAX_FILES_IN_ZIP_LABEL": "Max. files of zip archive",
|
||||
"NO_CONTACT_EMAIL": "no contact email",
|
||||
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Max. size of uploaded files (in Mb)",
|
||||
"TITLE": "Application configuration",
|
||||
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)"
|
||||
},
|
||||
"BACK_TO_ADMIN": "Back to admin",
|
||||
"CONFIRM_USER_ACCOUNT_DELETION": "Are you sure you want to delete {0} account? All data will be deleted, this cannot be undone.",
|
||||
"CONFIRM_USER_PASSWORD_RESET": "Are you sure you want to reset {0} password?",
|
||||
"CURRENT_EMAIL": "Current email",
|
||||
"DELETE_USER": "Delete user",
|
||||
"EMAIL_SENDING_DISABLED": "Email sending is disabled.",
|
||||
"ENABLE_DISABLE_SPORTS": "Enable/disable sports.",
|
||||
"NEW_EMAIL": "New email",
|
||||
"PASSWORD_RESET_SUCCESSFUL": "The password has been reset.",
|
||||
"REGISTRATION_DISABLED": "Registration is currently disabled.",
|
||||
"REGISTRATION_ENABLED": "Registration is currently enabled.",
|
||||
"RESET_USER_PASSWORD": "Reset password",
|
||||
"SPORTS": {
|
||||
"TABLE": {
|
||||
"ACTIVE": "Active",
|
||||
@ -28,7 +38,9 @@
|
||||
"TITLE": "Sports administration"
|
||||
},
|
||||
"UPDATE_APPLICATION_DESCRIPTION": "Update application configuration (maximum number of registered users, maximum files size).",
|
||||
"UPDATE_USER_EMAIL": "Update email",
|
||||
"USER": "user | users",
|
||||
"USER_EMAIL_UPDATE_SUCCESSFUL": "The email address has been updated.",
|
||||
"USERS": {
|
||||
"TABLE": {
|
||||
"ADD_ADMIN_RIGHTS": "Add admin rights",
|
||||
@ -38,6 +50,7 @@
|
||||
"ORDER_BY": {
|
||||
"ADMIN": "admin status",
|
||||
"CREATED_AT": "registration date",
|
||||
"IS_ACTIVE": "account status",
|
||||
"USERNAME": "username",
|
||||
"WORKOUTS_COUNT": "workout count"
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Error. Please try again or contact the administrator.",
|
||||
"email: valid email must be provided": "Email: valid email must be provided.",
|
||||
"error during gpx processing": "Error during gpx processing.",
|
||||
"error during gpx file parsing": "Error during gpx file parsing.",
|
||||
"error on getting configuration": "Error on getting configuration.",
|
||||
"error when updating configuration": "Error when updating configuration",
|
||||
"error, please try again or contact the administrator": "Error, please try again or contact the administrator.",
|
||||
@ -13,15 +15,17 @@
|
||||
"invalid token, please log in again": "Invalid token, please log in again.",
|
||||
"invalid token, please request a new token": "Invalid token, please log in again.",
|
||||
"Network Error": "Network Error.",
|
||||
"new email must be different than curent email": "The new email must be different than curent email",
|
||||
"no file part": "No file provided.",
|
||||
"no selected file": "No selected file.",
|
||||
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.",
|
||||
"provide a valid auth token": "Provide a valid auth token.",
|
||||
"sorry, that user already exists": "Sorry, that user already exists.",
|
||||
"sorry, that username is already taken": "Sorry, that username is already taken.",
|
||||
"sport does not exist": "Sport does not exist.",
|
||||
"signature expired, please log in again": "Signature expired. Please log in again.",
|
||||
"successfully registered": "Successfully registered.",
|
||||
"user does not exist": "User does not exist.",
|
||||
"valid email must be provided for admin contact": "A valid email must be provided for administrator contact",
|
||||
"you can not delete your account, no other user has admin rights": "You can not delete your account, no other user has admin rights.",
|
||||
"you do not have permissions": "You do not have permissions."
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"ACCOUNT-CONFIRMATION-RESEND": "Resend confirmation email",
|
||||
"BACK": "Back",
|
||||
"CANCEL": "Cancel",
|
||||
"CLEAR_FILTER": "Clear filters",
|
||||
|
@ -1,6 +1,9 @@
|
||||
{
|
||||
"ABOUT": "about",
|
||||
"CONFIRMATION": "Confirmation",
|
||||
"CONTACT": "contact",
|
||||
"DAY": "day | days",
|
||||
"DOCUMENTATION": "documentation",
|
||||
"HOME": "Home",
|
||||
"HERE": "here",
|
||||
"SELECTS": {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import AboutTranslations from './about.json'
|
||||
import AdministrationTranslations from './administration.json'
|
||||
import ApiTranslations from './api.json'
|
||||
import ButtonsTranslations from './buttons.json'
|
||||
@ -10,6 +11,7 @@ import UserTranslations from './user.json'
|
||||
import WorkoutsTranslations from './workouts.json'
|
||||
|
||||
export default {
|
||||
about: AboutTranslations,
|
||||
admin: AdministrationTranslations,
|
||||
api: ApiTranslations,
|
||||
buttons: ButtonsTranslations,
|
||||
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"UNKNOWN": "Error. Please try again or contact the administrator.",
|
||||
"APP_ERROR": "The application seems encounter some issues.<br />Please try later or contact the administrator.",
|
||||
"APP_ERROR": "The application seems to have encountered some issues.<br />Please try again later or contact the administrator.",
|
||||
"NOT_FOUND": {
|
||||
"PAGE": "Page not found",
|
||||
"WORKOUT": "Workout not found"
|
||||
}
|
||||
},
|
||||
"SOMETHING_WRONG": "Something went wrong"
|
||||
}
|
@ -1,29 +1,60 @@
|
||||
{
|
||||
"ACCOUNT_CONFIRMATION_NOT_RECEIVED": "Didn't received instructions?",
|
||||
"ACCOUNT_CONFIRMATION_SENT": "Check your email. A new confirmation email has been sent to the address provided.",
|
||||
"ADMIN": "Admin",
|
||||
"ALREADY_HAVE_ACCOUNT": "Already have an account?",
|
||||
"CONFIRM_ACCOUNT_DELETION": "Are you sure you want to delete your account? All data will be deleted, this cannot be undone",
|
||||
"CURRENT_PASSWORD": "Current password",
|
||||
"EMAIL": "Email",
|
||||
"ENTER_EMAIL": "Enter an email address",
|
||||
"EMAIL_INFO": "Enter a valid email address.",
|
||||
"ENTER_PASSWORD": "Enter a password",
|
||||
"ENTER_PASSWORD_CONFIRMATION": "Confirm the password",
|
||||
"FILTER_ON_USERNAME": "Filter on username",
|
||||
"HIDE_PASSWORD": "hide password",
|
||||
"INVALID_TOKEN": "Invalid token, please request a new password reset.",
|
||||
"LANGUAGE": "Language",
|
||||
"LOG_IN": "log in",
|
||||
"LOGIN": "Login",
|
||||
"LOGOUT": "Logout",
|
||||
"NEW_PASSWORD": "New password",
|
||||
"NO_USERS_FOUND": "No users found.",
|
||||
"PASSWORD": "Password",
|
||||
"PASSWORD_CONFIRM": "Confirm Password",
|
||||
"PASSWORD_CONFIRMATION": "Password confirmation",
|
||||
"PASSWORD_INFO": "At least 8 characters required.",
|
||||
"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_STRENGTH": {
|
||||
"WEAK": "weak",
|
||||
"AVERAGE": "average",
|
||||
"GOOD": "good",
|
||||
"STRONG": "strong",
|
||||
"LABEL": "password strength",
|
||||
"SUGGESTIONS": {
|
||||
"l33t": "Avoid predictable letter substitutions like {'@'} for a.",
|
||||
"reverseWords": "Avoid reversed spellings of common words.",
|
||||
"allUppercase": "Capitalize some, but not all letters.",
|
||||
"capitalization": "Capitalize more than the first letter.",
|
||||
"dates": "Avoid dates and years that are associated with you.",
|
||||
"recentYears": "Avoid recent years.",
|
||||
"associatedYears": "Avoid years that are associated with you.",
|
||||
"sequences": "Avoid common character sequences.",
|
||||
"repeated": "Avoid repeated words and characters.",
|
||||
"longerKeyboardPattern": "Use longer keyboard patterns and change typing direction multiple times.",
|
||||
"anotherWord": "Add more words that are less common.",
|
||||
"useWords": "Use multiple words, but avoid common phrases.",
|
||||
"noNeed": "You can create strong passwords without using symbols, numbers, or uppercase letters.",
|
||||
"pwned": "If you use this password elsewhere, you should change it."
|
||||
}
|
||||
},
|
||||
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
||||
"PROFILE": {
|
||||
"ACCOUNT_EDITION": "Account edition",
|
||||
"BACK_TO_PROFILE": "Back to profile",
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Birth date",
|
||||
"EDIT": "Edit profile",
|
||||
"EDIT_PREFERENCES": "Edit preferences",
|
||||
"EDIT_SPORTS_PREFERENCES": "Edit sports preferences",
|
||||
"ERRORED_EMAIL_UPDATE": "Please {0} to change your email address again or contact the administrator",
|
||||
"FIRST_NAME": "First name",
|
||||
"FIRST_DAY_OF_WEEK": "First day of week",
|
||||
"LANGUAGE": "Language",
|
||||
@ -40,6 +71,7 @@
|
||||
"SPORTS_EDITION": "Sports preferences edition",
|
||||
"SUNDAY": "Sunday",
|
||||
"TABS": {
|
||||
"ACCOUNT": "account",
|
||||
"PICTURE": "picture",
|
||||
"PREFERENCES": "preferences",
|
||||
"PROFILE": "profile",
|
||||
@ -53,6 +85,10 @@
|
||||
"LABEL": "label",
|
||||
"STOPPED_SPEED_THRESHOLD": "stopped speed threshold"
|
||||
},
|
||||
"SUCCESSFUL_EMAIL_UPDATE": "Your account has been updated successfully. Please check your email to confirm your new email address.",
|
||||
"SUCCESSFUL_REGISTRATION": "Your account has been created successfully.",
|
||||
"SUCCESSFUL_REGISTRATION_WITH_EMAIL": "A link to activate your account has been emailed to the address provided.",
|
||||
"SUCCESSFUL_UPDATE": "Your account has been updated successfully.",
|
||||
"UNITS": {
|
||||
"LABEL": "Units for distance",
|
||||
"IMPERIAL": "Imperial system (ft, mi)",
|
||||
@ -61,8 +97,12 @@
|
||||
"TIMEZONE": "Timezone"
|
||||
},
|
||||
"REGISTER": "Register",
|
||||
"RESENT_ACCOUNT_CONFIRMATION": "Resend account confirmation email",
|
||||
"REGISTER_DISABLED": "Sorry, registration is disabled.",
|
||||
"RESET_PASSWORD": "Reset your password",
|
||||
"SHOW_PASSWORD": "show password",
|
||||
"THIS_USER_ACCOUNT_IS_INACTIVE": "This user account is inactive.",
|
||||
"USER_PICTURE": "user picture",
|
||||
"USERNAME": "Username"
|
||||
"USERNAME": "Username",
|
||||
"USERNAME_INFO": "3 to 30 characters required, only alphanumeric characters and the underscore character \"_\" allowed."
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
||||
"PREVIOUS_WORKOUT": "Previous workout",
|
||||
"RECORD": "record | records",
|
||||
"RECORD_AS": "Ave. speed",
|
||||
"RECORD_FD": "Farest distance",
|
||||
"RECORD_FD": "Farthest distance",
|
||||
"RECORD_LD": "Longest duration",
|
||||
"RECORD_MS": "Max. speed",
|
||||
"REMAINING_CHARS": "remaining characters",
|
||||
|
6
fittrackee_client/src/locales/fr/about.json
Normal file
6
fittrackee_client/src/locales/fr/about.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"CONTACT_ADMIN": "Contacter l'administrateur",
|
||||
"FITTRACKEE_DESCRIPTION": "<strong>FitTrackee</strong> est un <em>tracker</em> d'activités sportives (en extérieur).",
|
||||
"FITTRACKEE_LICENSE": "sous licence {0} (en) ",
|
||||
"SOURCE_CODE": "Code source (en)"
|
||||
}
|
@ -1,23 +1,33 @@
|
||||
{
|
||||
"ACTION": "Action",
|
||||
"ACTIVATE_USER_ACCOUNT": "Activer le compte",
|
||||
"ACTIVE": "Actif",
|
||||
"ADMIN_RIGHTS_DELETE_USER_ACCOUNT": "Ajouter/retirer des droits d'administration, supprimer des comptes utilisateurs.",
|
||||
"ADMIN": "Admin",
|
||||
"ADMINISTRATION": "Administration",
|
||||
"APPLICATION": "Application",
|
||||
"APP_CONFIG": {
|
||||
"ADMIN_CONTACT": "Email de l'administrateur pour contact ",
|
||||
"MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ",
|
||||
"MAX_USERS_HELP": "Si égal à 0, pas limite d'inscription",
|
||||
"MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ",
|
||||
"NO_CONTACT_EMAIL": "non renseigné",
|
||||
"SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ",
|
||||
"TITLE": "Configuration de l'application",
|
||||
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip "
|
||||
},
|
||||
"BACK_TO_ADMIN": "Revenir à l'admin",
|
||||
"CONFIRM_USER_ACCOUNT_DELETION": "Etes-vous sûr de vouloir supprimer le compte de {0} ? Toutes les données seront définitivement.",
|
||||
"CONFIRM_USER_ACCOUNT_DELETION": "Êtes-vous sûr de vouloir supprimer le compte de l'utilisateur {0} ? Toutes les données seront définitivement.",
|
||||
"CONFIRM_USER_PASSWORD_RESET": "Êtes-vous sûr de vouloir réinitialiser le mot de passe de l'utilisateur {0} ?",
|
||||
"CURRENT_EMAIL": "Adresse email actuelle",
|
||||
"DELETE_USER": "Supprimer l'utilisateur",
|
||||
"EMAIL_SENDING_DISABLED": "L'envoi d'emails est désactivé.",
|
||||
"ENABLE_DISABLE_SPORTS": "Activer/désactiver des sports.",
|
||||
"NEW_EMAIL": "Nouvelle adresse email",
|
||||
"PASSWORD_RESET_SUCCESSFUL": "Le mot de passe a été réinitialisé.",
|
||||
"REGISTRATION_DISABLED": "Les inscriptions sont actuellement désactivées.",
|
||||
"REGISTRATION_ENABLED": "Les inscriptions sont actuellement activées.",
|
||||
"RESET_USER_PASSWORD": "Réinit. le mot de passe",
|
||||
"SPORTS": {
|
||||
"TABLE": {
|
||||
"ACTIVE": "Actif",
|
||||
@ -28,16 +38,19 @@
|
||||
"TITLE": "Administration - Sports"
|
||||
},
|
||||
"UPDATE_APPLICATION_DESCRIPTION": "Configurer l'application (nombre maximum d'utilisateurs inscrits, taille maximale des fichers).",
|
||||
"UPDATE_USER_EMAIL": "Changer l'email",
|
||||
"USER": "utilisateur | utilisateurs",
|
||||
"USER_EMAIL_UPDATE_SUCCESSFUL": "L'adresse email a été mise à jour.",
|
||||
"USERS": {
|
||||
"TABLE": {
|
||||
"ADD_ADMIN_RIGHTS": "Ajouter les drois d'admin",
|
||||
"REMOVE_ADMIN_RIGHTS": "Retirer les drois d'admin"
|
||||
"ADD_ADMIN_RIGHTS": "Ajouter les droits d'admin",
|
||||
"REMOVE_ADMIN_RIGHTS": "Retirer les droits d'admin"
|
||||
},
|
||||
"SELECTS": {
|
||||
"ORDER_BY": {
|
||||
"ADMIN": "status administrateur",
|
||||
"CREATED_AT": "date d'inscription",
|
||||
"IS_ACTIVE": "statut du compte",
|
||||
"USERNAME": "nom d'utilisateur",
|
||||
"WORKOUTS_COUNT": "nombre de séances"
|
||||
}
|
||||
|
@ -1,32 +1,36 @@
|
||||
{
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
|
||||
"email: valid email must be provided": "Email : une adresse email valide doit être fournie.",
|
||||
"error on getting configuration": "Erreur lors de la récupération de la configuration.",
|
||||
"error when updating configuration": "Erreur lors de la mise à jour de la configuration",
|
||||
"error, please try again or contact the administrator": "Erreur, veuillez réessayer ou contacter l'administrateur.",
|
||||
"error, registration is disabled": "Erreur, les inscriptions sont désactivées.",
|
||||
"file extension not allowed": "Extension de fichier non autorisée.",
|
||||
"file size is greater than the allowed size": "La taille du fichier est supérieure à la limite autorisée.",
|
||||
"invalid credentials": "Identifiants invalides.",
|
||||
"invalid payload": "Données fournies incorrectes.",
|
||||
"invalid token, please log in again": "Jeton de connexion invalide, merci de vous reconnecter.",
|
||||
"invalid token, please request a new token": "Jeton de connexion, merci de vous reconnecter.",
|
||||
"no file part": "Pas de fichier fourni.",
|
||||
"no selected file": "Pas de fichier sélectionné.",
|
||||
"Network Error": "Erreur Réseau.",
|
||||
"password: password and password confirmation do not match": "Mot de passe : les mots de passe saisis sont différents.",
|
||||
"provide a valid auth token": "Merci de fournir un jeton de connexion valide.",
|
||||
"sport does not exist": "Ce sport n'existe pas.",
|
||||
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
|
||||
"sorry, that user already exists": "Désolé, cet utilisateur existe déjà.",
|
||||
"successfully registered": "Inscription validée.",
|
||||
"user does not exist": "L'utilisateur n'existe pas",
|
||||
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
|
||||
"you do not have permissions": "Vous n'avez pas les permissions nécessaires."
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PREVIOUS": "précédent",
|
||||
"NEXT": "suivant"
|
||||
}
|
||||
}
|
||||
"ERROR": {
|
||||
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
|
||||
"email: valid email must be provided": "Courriel : une adresse électronique valide doit être fournie.",
|
||||
"error during gpx processing": "Erreur lors du traitement du fichier gpx.",
|
||||
"error during gpx file parsing": "Erreur lors de l'analyse du fichier.",
|
||||
"error on getting configuration": "Erreur lors de la récupération de la configuration.",
|
||||
"error when updating configuration": "Erreur lors de la mise à jour de la configuration",
|
||||
"error, please try again or contact the administrator": "Erreur, veuillez réessayer ou contacter l'administrateur.",
|
||||
"error, registration is disabled": "Erreur, les inscriptions sont désactivées.",
|
||||
"file extension not allowed": "Extension de fichier non autorisée.",
|
||||
"file size is greater than the allowed size": "La taille du fichier est supérieure à la limite autorisée.",
|
||||
"invalid credentials": "Identifiants invalides.",
|
||||
"invalid payload": "Données fournies incorrectes.",
|
||||
"invalid token, please log in again": "Jeton de connexion invalide, merci de vous reconnecter.",
|
||||
"invalid token, please request a new token": "Jeton de connexion, merci de vous reconnecter.",
|
||||
"no file part": "Pas de fichier fourni.",
|
||||
"no selected file": "Pas de fichier sélectionné.",
|
||||
"Network Error": "Erreur réseau.",
|
||||
"new email must be different than curent email": "La nouvelle addresse électronique doit être differente de l'adresse actuelle",
|
||||
"password: password and password confirmation do not match": "Mot de passe : les mots de passe saisis sont différents.",
|
||||
"provide a valid auth token": "Merci de fournir un jeton de connexion valide.",
|
||||
"sport does not exist": "Ce sport n'existe pas.",
|
||||
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
|
||||
"sorry, that username is already taken": "Désolé, ce nom d'utilisateur est déjà utilisé.",
|
||||
"successfully registered": "Inscription validée.",
|
||||
"user does not exist": "L'utilisateur n'existe pas.",
|
||||
"valid email must be provided for admin contact": "Une adresse électronique doit être fournie pour le contact de l'administrateur",
|
||||
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
|
||||
"you do not have permissions": "Vous n'avez pas les permissions nécessaires."
|
||||
},
|
||||
"PAGINATION": {
|
||||
"PREVIOUS": "précédent",
|
||||
"NEXT": "suivant"
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
{
|
||||
"BACK": "Précédent",
|
||||
"CANCEL": "Annuler",
|
||||
"CLEAR_FILTER": "Réinitialiser",
|
||||
"DELETE_MY_ACCOUNT": "Supprimer mon compte",
|
||||
"DISABLE": "Désactiver",
|
||||
"EDIT": "Modifier",
|
||||
"ENABLE": "Activer",
|
||||
"FILTER": "Filtrer",
|
||||
"LOGIN": "Se connecter",
|
||||
"NO": "Non",
|
||||
"REGISTER": "S'inscrire",
|
||||
"RESET": "Réinit.",
|
||||
"SUBMIT": "Valider",
|
||||
"YES": "Oui"
|
||||
}
|
||||
"ACCOUNT-CONFIRMATION-RESEND": "Renvoyer le message de confirmation",
|
||||
"BACK": "Précédent",
|
||||
"CANCEL": "Annuler",
|
||||
"CLEAR_FILTER": "Réinitialiser",
|
||||
"DELETE_MY_ACCOUNT": "Supprimer mon compte",
|
||||
"DISABLE": "Désactiver",
|
||||
"EDIT": "Modifier",
|
||||
"ENABLE": "Activer",
|
||||
"FILTER": "Filtrer",
|
||||
"LOGIN": "Se connecter",
|
||||
"NO": "Non",
|
||||
"REGISTER": "S'inscrire",
|
||||
"RESET": "Réinit.",
|
||||
"SUBMIT": "Valider",
|
||||
"YES": "Oui"
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
{
|
||||
"ABOUT": "à propos",
|
||||
"CONFIRMATION": "Confirmation",
|
||||
"CONTACT": "contact",
|
||||
"DAY": "jour | jours",
|
||||
"DOCUMENTATION": "documentation (en)",
|
||||
"HOME": "Accueil",
|
||||
"HERE": "ici",
|
||||
"SELECTS": {
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"DASHBOARD": "Tableau de Bord",
|
||||
"THIS_MONTH": "Ce mois"
|
||||
"DASHBOARD": "Tableau de bord",
|
||||
"THIS_MONTH": "Ce mois-ci"
|
||||
}
|
||||
|
@ -4,5 +4,6 @@
|
||||
"NOT_FOUND": {
|
||||
"PAGE": "Page introuvable",
|
||||
"WORKOUT": "Séance introuvable"
|
||||
}
|
||||
},
|
||||
"SOMETHING_WRONG": "Une erreur s'est produite"
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import AboutTranslations from './about.json'
|
||||
import AdministrationTranslations from './administration.json'
|
||||
import ApiTranslations from './api.json'
|
||||
import ButtonsTranslations from './buttons.json'
|
||||
@ -10,6 +11,7 @@ import UserTranslations from './user.json'
|
||||
import WorkoutsTranslations from './workouts.json'
|
||||
|
||||
export default {
|
||||
about: AboutTranslations,
|
||||
admin: AdministrationTranslations,
|
||||
api: ApiTranslations,
|
||||
buttons: ButtonsTranslations,
|
||||
|
@ -1,38 +1,38 @@
|
||||
{
|
||||
"Cycling (Sport)": {
|
||||
"LABEL": "Vélo (Sport)"
|
||||
},
|
||||
"Cycling (Transport)": {
|
||||
"LABEL": "Vélo (Transport)"
|
||||
},
|
||||
"Hiking": {
|
||||
"LABEL": "Randonnée"
|
||||
},
|
||||
"Mountain Biking": {
|
||||
"LABEL": "VTT"
|
||||
},
|
||||
"Mountain Biking (Electric)": {
|
||||
"LABEL": "VTT (Electrique)"
|
||||
},
|
||||
"Rowing": {
|
||||
"LABEL": "Aviron"
|
||||
},
|
||||
"Running": {
|
||||
"LABEL": "Course"
|
||||
},
|
||||
"Skiing (Alpine)": {
|
||||
"LABEL": "Ski (Alpin)"
|
||||
},
|
||||
"Skiing (Cross Country)": {
|
||||
"LABEL": "Ski (Randonnée)"
|
||||
},
|
||||
"Snowshoes": {
|
||||
"LABEL": "Raquettes"
|
||||
},
|
||||
"Trail": {
|
||||
"LABEL": "Trail"
|
||||
},
|
||||
"Walking": {
|
||||
"LABEL": "Marche"
|
||||
}
|
||||
}
|
||||
"Cycling (Sport)": {
|
||||
"LABEL": "Vélo (Sport)"
|
||||
},
|
||||
"Cycling (Transport)": {
|
||||
"LABEL": "Vélo (Transport)"
|
||||
},
|
||||
"Hiking": {
|
||||
"LABEL": "Randonnée"
|
||||
},
|
||||
"Mountain Biking": {
|
||||
"LABEL": "VTT"
|
||||
},
|
||||
"Mountain Biking (Electric)": {
|
||||
"LABEL": "VTT (Électrique)"
|
||||
},
|
||||
"Rowing": {
|
||||
"LABEL": "Aviron"
|
||||
},
|
||||
"Running": {
|
||||
"LABEL": "Course"
|
||||
},
|
||||
"Skiing (Alpine)": {
|
||||
"LABEL": "Ski (Alpin)"
|
||||
},
|
||||
"Skiing (Cross Country)": {
|
||||
"LABEL": "Ski (Randonnée)"
|
||||
},
|
||||
"Snowshoes": {
|
||||
"LABEL": "Raquettes"
|
||||
},
|
||||
"Trail": {
|
||||
"LABEL": "Trail"
|
||||
},
|
||||
"Walking": {
|
||||
"LABEL": "Marche"
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +1,108 @@
|
||||
{
|
||||
"ADMIN": "Admin",
|
||||
"ALREADY_HAVE_ACCOUNT": "Vous avez déjà un compte ?",
|
||||
"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 votre 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",
|
||||
"BIRTH_DATE": "Date de naissance",
|
||||
"EDIT": "Modifier le profil",
|
||||
"EDIT_PREFERENCES": "Modifier les préférences",
|
||||
"EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports",
|
||||
"FIRST_DAY_OF_WEEK": "Premier jour de la semaine",
|
||||
"FIRST_NAME": "Prénom",
|
||||
"ACCOUNT_CONFIRMATION_NOT_RECEIVED": "Vous n'avez pas reçu les instructions ?",
|
||||
"ACCOUNT_CONFIRMATION_SENT": "Vérifiez votre boite mail. Un nouvel email de confirmation a été envoyé à l'adresse email fournie.",
|
||||
"ADMIN": "Admin",
|
||||
"ALREADY_HAVE_ACCOUNT": "Vous avez déjà un compte ?",
|
||||
"CONFIRM_ACCOUNT_DELETION": "Êtes-vous sûr de vouloir supprimer votre compte ? Toutes les données seront définitivement effacés.",
|
||||
"CURRENT_PASSWORD": "Mot de passe actuel",
|
||||
"EMAIL": "Email",
|
||||
"EMAIL_INFO": "Saisir une adresse email valide.",
|
||||
"ENTER_PASSWORD": "Saisir un mot de passe",
|
||||
"FILTER_ON_USERNAME": "Filtrer sur le nom d'utilisateur",
|
||||
"HIDE_PASSWORD": "masquer le mot de passe",
|
||||
"INVALID_TOKEN": "Jeton invalide, veuillez demander une nouvelle réinitialisation de mot de passe.",
|
||||
"LANGUAGE": "Langue",
|
||||
"LAST_NAME": "Nom",
|
||||
"LOCATION": "Lieu",
|
||||
"MONDAY": "Lundi",
|
||||
"PICTURE": "Image de profil",
|
||||
"PICTURE_EDITION": "Mise à jour de l'image de profil",
|
||||
"PICTURE_UPDATE": "Mettre à jour l'image",
|
||||
"PICTURE_REMOVE": "Supprimer",
|
||||
"PREFERENCES_EDITION": "Mise à jour des préférences",
|
||||
"PROFILE_EDITION": "Mise à jour du profil",
|
||||
"REGISTRATION_DATE": "Date d'inscription",
|
||||
"SPORTS_EDITION": "Mise à jour des préférences des sports",
|
||||
"SUNDAY": "Dimanche",
|
||||
"TABS": {
|
||||
"PICTURE": "image",
|
||||
"PREFERENCES": "préférences",
|
||||
"PROFILE": "profil",
|
||||
"SPORTS": "sports"
|
||||
"LOG_IN": "connecter",
|
||||
"LOGIN": "Se connecter",
|
||||
"LOGOUT": "Se déconnecter",
|
||||
"NEW_PASSWORD": "Nouveau mot de passe",
|
||||
"NO_USERS_FOUND": "Aucun utilisateur trouvé.",
|
||||
"PASSWORD": "Mot de passe",
|
||||
"PASSWORD_INFO": "8 caractères minimum.",
|
||||
"PASSWORD_FORGOTTEN": "Mot de passe oublié ?",
|
||||
"PASSWORD_RESET": "Réinitialisation du mot de passe",
|
||||
"PASSWORD_SENT_EMAIL_TEXT": "Vérifiez votre 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_STRENGTH": {
|
||||
"WEAK": "faible",
|
||||
"AVERAGE": "moyenne",
|
||||
"GOOD": "bonne",
|
||||
"STRONG": "forte",
|
||||
"LABEL": "robustesse du mot de passe ",
|
||||
"SUGGESTIONS": {
|
||||
"l33t": "Évitez les substitutions de lettres prévisibles comme {'@'} pour a.",
|
||||
"reverseWords": "Évitez les orthographes inversées des mots courants.",
|
||||
"allUppercase": "Mettez quelques lettres en majuscules, mais pas toutes.",
|
||||
"capitalization": "Capitalisez mais pas seulement la première lettre.",
|
||||
"dates": "Évitez les dates et les années qui vous sont associées. (ex : date ou année de naissance).",
|
||||
"recentYears": "Évitez les dernières années.",
|
||||
"associatedYears": "Évitez les années qui vous sont associées. (ex : date de naissance).",
|
||||
"sequences": "Évitez les séquences de caractères courantes.",
|
||||
"repeated": "Évitez les mots et les caractères répétés.",
|
||||
"longerKeyboardPattern": "Utilisez des motifs de clavier plus longs et changez de sens de frappe plusieurs fois.",
|
||||
"anotherWord": "Ajoutez des mots moins courants.",
|
||||
"useWords": "Utilisez plusieurs mots, mais évitez les phrases courantes.",
|
||||
"noNeed": "Vous pouvez créer des mots de passe forts sans utiliser de symboles, de chiffres ou de lettres majuscules.",
|
||||
"pwned": "Si vous utilisez ce mot de passe ailleurs, vous devriez le modifier."
|
||||
}
|
||||
},
|
||||
"UNITS": {
|
||||
"LABEL": "Unités pour les distances ",
|
||||
"IMPERIAL": "Système impérial (ft, mi)",
|
||||
"METRIC": "Système métrique (m, km)"
|
||||
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
||||
"PROFILE": {
|
||||
"ACCOUNT_EDITION": "Mise à jour du compte",
|
||||
"BACK_TO_PROFILE": "Revenir au profil",
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Date de naissance",
|
||||
"EDIT": "Modifier le profil",
|
||||
"EDIT_PREFERENCES": "Modifier les préférences",
|
||||
"EDIT_SPORTS_PREFERENCES": "Modifier les préférences des sports",
|
||||
"ERRORED_EMAIL_UPDATE": "Veuillez vous {0} pour changer de nouveau votre adresse email ou contacter l'administrateur",
|
||||
"FIRST_DAY_OF_WEEK": "Premier jour de la semaine",
|
||||
"FIRST_NAME": "Prénom",
|
||||
"LANGUAGE": "Langue",
|
||||
"LAST_NAME": "Nom",
|
||||
"LOCATION": "Lieu",
|
||||
"MONDAY": "Lundi",
|
||||
"PICTURE": "Image de profil",
|
||||
"PICTURE_EDITION": "Mise à jour de l'image de profil",
|
||||
"PICTURE_UPDATE": "Mettre à jour l'image",
|
||||
"PICTURE_REMOVE": "Supprimer",
|
||||
"PREFERENCES_EDITION": "Mise à jour des préférences",
|
||||
"PROFILE_EDITION": "Mise à jour du profil",
|
||||
"REGISTRATION_DATE": "Date d'inscription",
|
||||
"SPORTS_EDITION": "Mise à jour des préférences des sports",
|
||||
"SUNDAY": "Dimanche",
|
||||
"TABS": {
|
||||
"ACCOUNT": "compte",
|
||||
"PICTURE": "image",
|
||||
"PREFERENCES": "préférences",
|
||||
"PROFILE": "profil",
|
||||
"SPORTS": "sports"
|
||||
},
|
||||
"UNITS": {
|
||||
"LABEL": "Unités pour les distances ",
|
||||
"IMPERIAL": "Système impérial (ft, mi)",
|
||||
"METRIC": "Système métrique (m, km)"
|
||||
},
|
||||
"SPORT": {
|
||||
"ACTION": "action",
|
||||
"COLOR": "couleur",
|
||||
"DISABLED_BY_ADMIN": "désactivé par l'administrateur",
|
||||
"IS_ACTIVE": "actif",
|
||||
"LABEL": "label",
|
||||
"STOPPED_SPEED_THRESHOLD": "seuil de vitesse arrêtée"
|
||||
},
|
||||
"SUCCESSFUL_EMAIL_UPDATE": "Votre compte a été modifié avec succès. Veuillez vérifier votre boite email pour valider votre nouvelle adresse email.",
|
||||
"SUCCESSFUL_REGISTRATION": "Votre compte a été créé avec succès.",
|
||||
"SUCCESSFUL_REGISTRATION_WITH_EMAIL": "Un lien pour activer votre compte a été envoyé à l'adresse email fournie.",
|
||||
"SUCCESSFUL_UPDATE": "Votre compte a été modifié avec succès.",
|
||||
"TIMEZONE": "Fuseau horaire"
|
||||
},
|
||||
"SPORT": {
|
||||
"ACTION": "action",
|
||||
"COLOR": "couleur",
|
||||
"DISABLED_BY_ADMIN": "désactivé par l'administrateur",
|
||||
"IS_ACTIVE": "actif",
|
||||
"LABEL": "label",
|
||||
"STOPPED_SPEED_THRESHOLD": "seuil de vitesse arrêtée"
|
||||
},
|
||||
"TIMEZONE": "Fuseau horaire"
|
||||
},
|
||||
"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"
|
||||
"REGISTER": "S'inscrire",
|
||||
"REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.",
|
||||
"RESENT_ACCOUNT_CONFIRMATION": "Envoyer à nouveau l'email de confirmation de compte",
|
||||
"RESET_PASSWORD": "Réinitialiser votre mot de passe",
|
||||
"SHOW_PASSWORD": "afficher le mot de passe",
|
||||
"THIS_USER_ACCOUNT_IS_INACTIVE": "Le compte de cet utilisateur est inactif.",
|
||||
"USER_PICTURE": "photo de l'utilisateur",
|
||||
"USERNAME": "Nom d'utilisateur",
|
||||
"USERNAME_INFO": "3 à 30 caractères requis, seuls les caractères alphanumériques et le caractère \"_\" sont autorisés."
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ import Profile from '@/components/User/ProfileDisplay/index.vue'
|
||||
import UserInfos from '@/components/User/ProfileDisplay/UserInfos.vue'
|
||||
import UserPreferences from '@/components/User/ProfileDisplay/UserPreferences.vue'
|
||||
import ProfileEdition from '@/components/User/ProfileEdition/index.vue'
|
||||
import UserAccountEdition from '@/components/User/ProfileEdition/UserAccountEdition.vue'
|
||||
import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue'
|
||||
import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.vue'
|
||||
import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPreferencesEdition.vue'
|
||||
import UserSportPreferences from '@/components/User/UserSportPreferences.vue'
|
||||
import store from '@/store'
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import AboutView from '@/views/AboutView.vue'
|
||||
import Dashboard from '@/views/Dashboard.vue'
|
||||
import NotFoundView from '@/views/NotFoundView.vue'
|
||||
import LoginOrRegister from '@/views/user/LoginOrRegister.vue'
|
||||
@ -42,6 +44,32 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: LoginOrRegister,
|
||||
props: { action: 'register' },
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation',
|
||||
name: 'AccountConfirmation',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: 'profile' */ '@/views/user/AccountConfirmationView.vue'
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation/resend',
|
||||
name: 'AccountConfirmationResend',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/AccountConfirmationResendView.vue'
|
||||
),
|
||||
props: { action: 'account-confirmation-resend' },
|
||||
},
|
||||
{
|
||||
path: '/account-confirmation/email-sent',
|
||||
name: 'AccountConfirmationEmailSend',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: 'reset' */ '@/views/user/AccountConfirmationResendView.vue'
|
||||
),
|
||||
props: { action: 'email-sent' },
|
||||
},
|
||||
{
|
||||
path: '/password-reset/sent',
|
||||
name: 'PasswordEmailSent',
|
||||
@ -78,6 +106,14 @@ const routes: Array<RouteRecordRaw> = [
|
||||
),
|
||||
props: { action: 'reset' },
|
||||
},
|
||||
{
|
||||
path: '/email-update',
|
||||
name: 'EmailUpdate',
|
||||
component: () =>
|
||||
import(
|
||||
/* webpackChunkName: 'profile' */ '@/views/user/EmailUpdateView.vue'
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
@ -123,6 +159,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'UserInfosEdition',
|
||||
component: UserInfosEdition,
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
name: 'UserAccountEdition',
|
||||
component: UserAccountEdition,
|
||||
},
|
||||
{
|
||||
path: 'picture',
|
||||
name: 'UserPictureEdition',
|
||||
@ -220,6 +261,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'SportsAdministration',
|
||||
component: AdminSports,
|
||||
},
|
||||
{
|
||||
path: 'users/:username',
|
||||
name: 'UserFromAdmin',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: 'profile' */ '@/views/user/UserView.vue'),
|
||||
props: { fromAdmin: true },
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
name: 'UsersAdministration',
|
||||
@ -227,6 +275,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: AboutView,
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
@ -246,18 +299,27 @@ const pathsWithoutAuthentication = [
|
||||
'/password-reset/request',
|
||||
'/password-reset/sent',
|
||||
'/register',
|
||||
'/account-confirmation',
|
||||
'/account-confirmation/resend',
|
||||
'/account-confirmation/email-sent',
|
||||
]
|
||||
|
||||
const pathsWithoutChecks = ['/email-update', '/about']
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
store
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.CHECK_AUTH_USER)
|
||||
.then(() => {
|
||||
if (pathsWithoutChecks.includes(to.path)) {
|
||||
return next()
|
||||
}
|
||||
if (
|
||||
store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED] &&
|
||||
pathsWithoutAuthentication.includes(to.path)
|
||||
) {
|
||||
return next('/')
|
||||
} else if (
|
||||
}
|
||||
if (
|
||||
!store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED] &&
|
||||
!pathsWithoutAuthentication.includes(to.path)
|
||||
) {
|
||||
|
@ -142,6 +142,20 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-info {
|
||||
color: var(--alert-color);
|
||||
font-size: 0.8em;
|
||||
margin-top: -0.2 * $default-margin;
|
||||
padding: 0 $default-padding * 1.5;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin: $default-margin * 2 0;
|
||||
background-color: var(--success-background-color);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.upper {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@ -169,7 +183,7 @@ button {
|
||||
|
||||
.no-map {
|
||||
background-color: var(--workout-no-map-bg-color);
|
||||
background-image: url('/img/workouts/map.svg');
|
||||
background-image: url('~@/assets/img/workouts/map.svg');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@ -247,10 +261,6 @@ button {
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
width: 100%;
|
||||
margin: 0 auto 50px auto;
|
||||
|
||||
&.with-margin {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,12 +44,14 @@
|
||||
--footer-border-color: #ebeef3;
|
||||
--footer-color: #8b8c8c;
|
||||
|
||||
--alert-background-color: #c8cdd3;
|
||||
--alert-background-color: #d6dde3;
|
||||
--alert-color: #3f3f3f;
|
||||
--info-background-color: #e5e7ea;
|
||||
--info-color: var(--app-color);
|
||||
--error-background-color: #ffd2d2;
|
||||
--error-color: #db1924;
|
||||
--success-background-color: #d9ecd9;
|
||||
--success-color: #306430;
|
||||
|
||||
--disabled-background-color: #e0e0e0;
|
||||
--disabled-color: #a3a3a3;
|
||||
@ -66,6 +68,12 @@
|
||||
--cell-heading-bg-color: #eeeeee;
|
||||
--cell-heading-color: #696969;
|
||||
|
||||
--svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color))
|
||||
--svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color));
|
||||
|
||||
--password-bg-color: #d7dadf;
|
||||
--password-color-weak: #e46d6e;
|
||||
--password-color-medium: #f8bc4a;
|
||||
--password-color-good: #acc578;
|
||||
--password-color-strong: #57c255;
|
||||
|
||||
}
|
@ -2,7 +2,6 @@ import { ActionContext, ActionTree } from 'vuex'
|
||||
|
||||
import authApi from '@/api/authApi'
|
||||
import api from '@/api/defaultApi'
|
||||
import createI18n from '@/i18n'
|
||||
import router from '@/router'
|
||||
import {
|
||||
AUTH_USER_STORE,
|
||||
@ -20,8 +19,10 @@ import { IRootState } from '@/store/modules/root/types'
|
||||
import { deleteUserAccount } from '@/store/modules/users/actions'
|
||||
import {
|
||||
ILoginOrRegisterData,
|
||||
IUserAccountPayload,
|
||||
IUserDeletionPayload,
|
||||
IUserPasswordPayload,
|
||||
IUserAccountUpdatePayload,
|
||||
IUserEmailPayload,
|
||||
IUserPasswordResetPayload,
|
||||
IUserPayload,
|
||||
IUserPicturePayload,
|
||||
@ -30,8 +31,6 @@ import {
|
||||
} from '@/types/user'
|
||||
import { handleError } from '@/utils'
|
||||
|
||||
const { locale } = createI18n.global
|
||||
|
||||
const removeAuthUserData = (
|
||||
context: ActionContext<IAuthUserState, IRootState>
|
||||
) => {
|
||||
@ -61,6 +60,56 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
context.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
|
||||
}
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.CONFIRM_ACCOUNT](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountUpdatePayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
api
|
||||
.post('auth/account/confirm', { token: payload.token })
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
const token = res.data.auth_token
|
||||
window.localStorage.setItem('authToken', token)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_TOKEN, token)
|
||||
context
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
|
||||
.then(() => router.push('/'))
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
handleError(context, error)
|
||||
})
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.CONFIRM_EMAIL](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountUpdatePayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
api
|
||||
.post('/auth/email/update', { token: payload.token })
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, true)
|
||||
if (payload.refreshUser) {
|
||||
context
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
|
||||
.then(() => {
|
||||
return router.push('/profile/edit/account')
|
||||
})
|
||||
}
|
||||
router.push('/profile/edit/account')
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
handleError(context, error)
|
||||
})
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE](
|
||||
context: ActionContext<IAuthUserState, IRootState>
|
||||
): void {
|
||||
@ -74,11 +123,10 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
res.data.data
|
||||
)
|
||||
if (res.data.data.language) {
|
||||
context.commit(
|
||||
ROOT_STORE.MUTATIONS.UPDATE_LANG,
|
||||
context.dispatch(
|
||||
ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE,
|
||||
res.data.data.language
|
||||
)
|
||||
locale.value = res.data.data.language
|
||||
}
|
||||
context.dispatch(SPORTS_STORE.ACTIONS.GET_SPORTS)
|
||||
} else {
|
||||
@ -96,20 +144,35 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
data: ILoginOrRegisterData
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(
|
||||
AUTH_USER_STORE.MUTATIONS.UPDATE_IS_REGISTRATION_SUCCESS,
|
||||
false
|
||||
)
|
||||
api
|
||||
.post(`/auth/${data.actionType}`, data.formData)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
const token = res.data.auth_token
|
||||
window.localStorage.setItem('authToken', token)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_TOKEN, token)
|
||||
context
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
|
||||
.then(() =>
|
||||
router.push(
|
||||
typeof data.redirectUrl === 'string' ? data.redirectUrl : '/'
|
||||
if (data.actionType === 'login') {
|
||||
const token = res.data.auth_token
|
||||
window.localStorage.setItem('authToken', token)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_TOKEN, token)
|
||||
context
|
||||
.dispatch(AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE)
|
||||
.then(() =>
|
||||
router.push(
|
||||
typeof data.redirectUrl === 'string' ? data.redirectUrl : '/'
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
router
|
||||
.push('/login')
|
||||
.then(() =>
|
||||
context.commit(
|
||||
AUTH_USER_STORE.MUTATIONS.UPDATE_IS_REGISTRATION_SUCCESS,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
@ -145,6 +208,31 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
)
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_ACCOUNT](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, true)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
authApi
|
||||
.patch('auth/profile/edit/account', payload)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(
|
||||
AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE,
|
||||
res.data.data
|
||||
)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS, true)
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => handleError(context, error))
|
||||
.finally(() =>
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
)
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPreferencesPayload
|
||||
@ -159,12 +247,12 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE,
|
||||
res.data.data
|
||||
)
|
||||
context.commit(
|
||||
ROOT_STORE.MUTATIONS.UPDATE_LANG,
|
||||
res.data.data.language
|
||||
)
|
||||
locale.value = res.data.data.language
|
||||
router.push('/profile/preferences')
|
||||
context
|
||||
.dispatch(
|
||||
ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE,
|
||||
res.data.data.language
|
||||
)
|
||||
.then(() => router.push('/profile/preferences'))
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
@ -274,7 +362,7 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPasswordPayload
|
||||
payload: IUserEmailPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
api
|
||||
@ -288,6 +376,22 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
})
|
||||
.catch((error) => handleError(context, error))
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.RESEND_ACCOUNT_CONFIRMATION_EMAIL](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserEmailPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
api
|
||||
.post('auth/account/resend-confirmation', payload)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
router.push('/account-confirmation/email-sent')
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => handleError(context, error))
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPasswordResetPayload
|
||||
|
@ -1,13 +1,17 @@
|
||||
export enum AuthUserActions {
|
||||
CHECK_AUTH_USER = 'CHECK_AUTH_USER',
|
||||
CONFIRM_ACCOUNT = 'CONFIRM_ACCOUNT',
|
||||
CONFIRM_EMAIL = 'CONFIRM_EMAIL',
|
||||
DELETE_ACCOUNT = 'DELETE_ACCOUNT',
|
||||
DELETE_PICTURE = 'DELETE_PICTURE',
|
||||
GET_USER_PROFILE = 'GET_USER_PROFILE',
|
||||
LOGIN_OR_REGISTER = 'LOGIN_OR_REGISTER',
|
||||
LOGOUT = 'LOGOUT',
|
||||
SEND_PASSWORD_RESET_REQUEST = 'SEND_PASSWORD_RESET_REQUEST',
|
||||
RESEND_ACCOUNT_CONFIRMATION_EMAIL = 'RESEND_ACCOUNT_CONFIRMATION_EMAIL',
|
||||
RESET_USER_PASSWORD = 'RESET_USER_PASSWORD',
|
||||
RESET_USER_SPORT_PREFERENCES = 'RESET_USER_SPORT_PREFERENCES',
|
||||
UPDATE_USER_ACCOUNT = 'UPDATE_USER_ACCOUNT',
|
||||
UPDATE_USER_PICTURE = 'UPDATE_USER_PICTURE',
|
||||
UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE',
|
||||
UPDATE_USER_PREFERENCES = 'UPDATE_USER_PREFERENCES',
|
||||
@ -19,6 +23,8 @@ export enum AuthUserGetters {
|
||||
AUTH_USER_PROFILE = 'AUTH_USER_PROFILE',
|
||||
IS_ADMIN = 'IS_ADMIN',
|
||||
IS_AUTHENTICATED = 'IS_AUTHENTICATED',
|
||||
IS_SUCCESS = 'IS_SUCCESS',
|
||||
IS_REGISTRATION_SUCCESS = 'IS_REGISTRATION_SUCCESS',
|
||||
USER_LOADING = 'USER_LOADING',
|
||||
}
|
||||
|
||||
@ -26,5 +32,7 @@ export enum AuthUserMutations {
|
||||
CLEAR_AUTH_USER_TOKEN = 'CLEAR_AUTH_USER_TOKEN',
|
||||
UPDATE_AUTH_TOKEN = 'UPDATE_AUTH_TOKEN',
|
||||
UPDATE_AUTH_USER_PROFILE = 'UPDATE_AUTH_USER_PROFILE',
|
||||
UPDATE_IS_SUCCESS = 'UPDATE_USER_IS_SUCCESS',
|
||||
UPDATE_IS_REGISTRATION_SUCCESS = 'UPDATE_IS_REGISTRATION_SUCCESS',
|
||||
UPDATE_USER_LOADING = 'UPDATE_USER_LOADING',
|
||||
}
|
||||
|
@ -21,6 +21,14 @@ export const getters: GetterTree<IAuthUserState, IRootState> &
|
||||
[AUTH_USER_STORE.GETTERS.IS_ADMIN]: (state: IAuthUserState) => {
|
||||
return state.authUserProfile && state.authUserProfile.admin
|
||||
},
|
||||
[AUTH_USER_STORE.GETTERS.IS_REGISTRATION_SUCCESS]: (
|
||||
state: IAuthUserState
|
||||
) => {
|
||||
return state.isRegistrationSuccess
|
||||
},
|
||||
[AUTH_USER_STORE.GETTERS.IS_SUCCESS]: (state: IAuthUserState) => {
|
||||
return state.isSuccess
|
||||
},
|
||||
[AUTH_USER_STORE.GETTERS.USER_LOADING]: (state: IAuthUserState) => {
|
||||
return state.loading
|
||||
},
|
||||
|
@ -5,12 +5,12 @@ import {
|
||||
IAuthUserState,
|
||||
TAuthUserMutations,
|
||||
} from '@/store/modules/authUser/types'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
|
||||
export const mutations: MutationTree<IAuthUserState> & TAuthUserMutations = {
|
||||
[AUTH_USER_STORE.MUTATIONS.CLEAR_AUTH_USER_TOKEN](state: IAuthUserState) {
|
||||
state.authToken = null
|
||||
state.authUserProfile = <IUserProfile>{}
|
||||
state.authUserProfile = <IAuthUserProfile>{}
|
||||
},
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_TOKEN](
|
||||
state: IAuthUserState,
|
||||
@ -20,10 +20,22 @@ export const mutations: MutationTree<IAuthUserState> & TAuthUserMutations = {
|
||||
},
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE](
|
||||
state: IAuthUserState,
|
||||
authUserProfile: IUserProfile
|
||||
authUserProfile: IAuthUserProfile
|
||||
) {
|
||||
state.authUserProfile = authUserProfile
|
||||
},
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_IS_REGISTRATION_SUCCESS](
|
||||
state: IAuthUserState,
|
||||
isRegistrationSuccess: boolean
|
||||
) {
|
||||
state.isRegistrationSuccess = isRegistrationSuccess
|
||||
},
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS](
|
||||
state: IAuthUserState,
|
||||
isSuccess: boolean
|
||||
) {
|
||||
state.isSuccess = isSuccess
|
||||
},
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING](
|
||||
state: IAuthUserState,
|
||||
loading: boolean
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { IAuthUserState } from '@/store/modules/authUser/types'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
|
||||
export const authUserState: IAuthUserState = {
|
||||
authToken: null,
|
||||
authUserProfile: <IUserProfile>{},
|
||||
authUserProfile: <IAuthUserProfile>{},
|
||||
isSuccess: false,
|
||||
isRegistrationSuccess: false,
|
||||
loading: false,
|
||||
}
|
||||
|
@ -8,20 +8,24 @@ import {
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IRootState } from '@/store/modules/root/types'
|
||||
import {
|
||||
IUserProfile,
|
||||
IAuthUserProfile,
|
||||
ILoginOrRegisterData,
|
||||
IUserDeletionPayload,
|
||||
IUserPasswordPayload,
|
||||
IUserEmailPayload,
|
||||
IUserPasswordResetPayload,
|
||||
IUserPayload,
|
||||
IUserPicturePayload,
|
||||
IUserPreferencesPayload,
|
||||
IUserSportPreferencesPayload,
|
||||
IUserAccountPayload,
|
||||
IUserAccountUpdatePayload,
|
||||
} from '@/types/user'
|
||||
|
||||
export interface IAuthUserState {
|
||||
authToken: string | null
|
||||
authUserProfile: IUserProfile
|
||||
authUserProfile: IAuthUserProfile
|
||||
isRegistrationSuccess: boolean
|
||||
isSuccess: boolean
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
@ -30,6 +34,16 @@ export interface IAuthUserActions {
|
||||
context: ActionContext<IAuthUserState, IRootState>
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.CONFIRM_ACCOUNT](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountUpdatePayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.CONFIRM_EMAIL](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountUpdatePayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.GET_USER_PROFILE](
|
||||
context: ActionContext<IAuthUserState, IRootState>
|
||||
): void
|
||||
@ -48,6 +62,11 @@ export interface IAuthUserActions {
|
||||
payload: IUserPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_ACCOUNT](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserAccountPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPreferencesPayload
|
||||
@ -65,7 +84,12 @@ export interface IAuthUserActions {
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPasswordPayload
|
||||
payload: IUserEmailPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.RESEND_ACCOUNT_CONFIRMATION_EMAIL](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserEmailPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD](
|
||||
@ -93,12 +117,18 @@ export interface IAuthUserGetters {
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE](
|
||||
state: IAuthUserState
|
||||
): IUserProfile
|
||||
): IAuthUserProfile
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.IS_ADMIN](state: IAuthUserState): boolean
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED](state: IAuthUserState): boolean
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.IS_REGISTRATION_SUCCESS](
|
||||
state: IAuthUserState
|
||||
): boolean
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.IS_SUCCESS](state: IAuthUserState): boolean
|
||||
|
||||
[AUTH_USER_STORE.GETTERS.USER_LOADING](state: IAuthUserState): boolean
|
||||
}
|
||||
|
||||
@ -110,12 +140,20 @@ export type TAuthUserMutations<S = IAuthUserState> = {
|
||||
): void
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE](
|
||||
state: S,
|
||||
authUserProfile: IUserProfile
|
||||
authUserProfile: IAuthUserProfile
|
||||
): void
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_IS_SUCCESS](
|
||||
state: S,
|
||||
isSuccess: boolean
|
||||
): void
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING](
|
||||
state: S,
|
||||
loading: boolean
|
||||
): void
|
||||
[AUTH_USER_STORE.MUTATIONS.UPDATE_IS_REGISTRATION_SUCCESS](
|
||||
state: S,
|
||||
loading: boolean
|
||||
): void
|
||||
}
|
||||
|
||||
export type TAuthUserStoreModule<S = IAuthUserState> = Omit<
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
|
||||
import authApi from '@/api/authApi'
|
||||
import createI18n from '@/i18n'
|
||||
import router from '@/router'
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { IRootActions, IRootState } from '@/store/modules/root/types'
|
||||
import { TAppConfigForm } from '@/types/application'
|
||||
import { handleError } from '@/utils'
|
||||
|
||||
const { locale } = createI18n.global
|
||||
|
||||
export const actions: ActionTree<IRootState, IRootState> & IRootActions = {
|
||||
[ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG](
|
||||
context: ActionContext<IRootState, IRootState>
|
||||
@ -68,4 +71,12 @@ export const actions: ActionTree<IRootState, IRootState> & IRootActions = {
|
||||
})
|
||||
.catch((error) => handleError(context, error))
|
||||
},
|
||||
[ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE](
|
||||
context: ActionContext<IRootState, IRootState>,
|
||||
language: string
|
||||
): void {
|
||||
document.querySelector('html')?.setAttribute('lang', language)
|
||||
context.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, language)
|
||||
locale.value = language
|
||||
},
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ export enum RootActions {
|
||||
GET_APPLICATION_CONFIG = 'GET_APPLICATION_CONFIG',
|
||||
GET_APPLICATION_STATS = 'GET_APPLICATION_STATS',
|
||||
UPDATE_APPLICATION_CONFIG = 'UPDATE_APPLICATION_CONFIG',
|
||||
UPDATE_APPLICATION_LANGUAGE = 'UPDATE_APPLICATION_LANGUAGE',
|
||||
}
|
||||
|
||||
export enum RootGetters {
|
||||
|
@ -34,6 +34,10 @@ export interface IRootActions {
|
||||
context: ActionContext<IRootState, IRootState>,
|
||||
payload: TAppConfigForm
|
||||
): void
|
||||
[ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE](
|
||||
context: ActionContext<IRootState, IRootState>,
|
||||
langauge: string
|
||||
): void
|
||||
}
|
||||
|
||||
export interface IRootGetters {
|
||||
|
@ -104,14 +104,37 @@ export const actions: ActionTree<IUsersState, IRootState> & IUsersActions = {
|
||||
payload: IAdminUserPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS, false)
|
||||
const data: Record<string, boolean | string> = {}
|
||||
if (payload.admin !== undefined) {
|
||||
data.admin = payload.admin
|
||||
}
|
||||
if (payload.resetPassword) {
|
||||
data.reset_password = payload.resetPassword
|
||||
}
|
||||
if (payload.activate) {
|
||||
data.activate = payload.activate
|
||||
}
|
||||
if (payload.new_email !== undefined) {
|
||||
data.new_email = payload.new_email
|
||||
}
|
||||
authApi
|
||||
.patch(`users/${payload.username}`, { admin: payload.admin })
|
||||
.patch(`users/${payload.username}`, data)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(
|
||||
USERS_STORE.MUTATIONS.UPDATE_USER_IN_USERS,
|
||||
res.data.data.users[0]
|
||||
)
|
||||
if (payload.resetPassword || payload.new_email) {
|
||||
context.commit(USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS, true)
|
||||
}
|
||||
if (payload.activate || payload.new_email) {
|
||||
context.commit(
|
||||
USERS_STORE.MUTATIONS.UPDATE_USER,
|
||||
res.data.data.users[0]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ export enum UsersActions {
|
||||
export enum UsersGetters {
|
||||
USER = 'USER',
|
||||
USERS = 'USERS',
|
||||
USERS_IS_SUCCESS = 'USERS_IS_SUCCESS',
|
||||
USERS_LOADING = 'USERS_LOADING',
|
||||
USERS_PAGINATION = 'USERS_PAGINATION',
|
||||
}
|
||||
@ -20,4 +21,5 @@ export enum UsersMutations {
|
||||
UPDATE_USERS = 'UPDATE_USERS',
|
||||
UPDATE_USERS_LOADING = 'UPDATE_USERS_LOADING',
|
||||
UPDATE_USERS_PAGINATION = 'UPDATE_USERS_PAGINATION',
|
||||
UPDATE_IS_SUCCESS = 'UPDATE_IS_SUCCESS',
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ export const getters: GetterTree<IUsersState, IRootState> & IUsersGetters = {
|
||||
[USERS_STORE.GETTERS.USERS]: (state: IUsersState) => {
|
||||
return state.users
|
||||
},
|
||||
[USERS_STORE.GETTERS.USERS_IS_SUCCESS]: (state: IUsersState) => {
|
||||
return state.isSuccess
|
||||
},
|
||||
[USERS_STORE.GETTERS.USERS_LOADING]: (state: IUsersState) => {
|
||||
return state.loading
|
||||
},
|
||||
|
@ -38,4 +38,10 @@ export const mutations: MutationTree<IUsersState> & TUsersMutations = {
|
||||
) {
|
||||
state.pagination = pagination
|
||||
},
|
||||
[USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS](
|
||||
state: IUsersState,
|
||||
isSuccess: boolean
|
||||
) {
|
||||
state.isSuccess = isSuccess
|
||||
},
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ export const usersState: IUsersState = {
|
||||
user: <IUserProfile>{},
|
||||
users: [],
|
||||
loading: false,
|
||||
isSuccess: false,
|
||||
pagination: <IPagination>{},
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ export interface IUsersState {
|
||||
user: IUserProfile
|
||||
users: IUserProfile[]
|
||||
loading: boolean
|
||||
isSuccess: boolean
|
||||
pagination: IPagination
|
||||
}
|
||||
|
||||
@ -49,6 +50,7 @@ export interface IUsersActions {
|
||||
export interface IUsersGetters {
|
||||
[USERS_STORE.GETTERS.USER](state: IUsersState): IUserProfile
|
||||
[USERS_STORE.GETTERS.USERS](state: IUsersState): IUserProfile[]
|
||||
[USERS_STORE.GETTERS.USERS_IS_SUCCESS](state: IUsersState): boolean
|
||||
[USERS_STORE.GETTERS.USERS_LOADING](state: IUsersState): boolean
|
||||
[USERS_STORE.GETTERS.USERS_PAGINATION](state: IUsersState): IPagination
|
||||
}
|
||||
@ -65,6 +67,7 @@ export type TUsersMutations<S = IUsersState> = {
|
||||
state: S,
|
||||
pagination: IPagination
|
||||
): void
|
||||
[USERS_STORE.MUTATIONS.UPDATE_IS_SUCCESS](state: S, isSuccess: boolean): void
|
||||
}
|
||||
|
||||
export type TUsersStoreModule<S = IUsersState> = Omit<
|
||||
|
@ -7,11 +7,12 @@ export interface IPagination {
|
||||
}
|
||||
|
||||
export type TPaginationPayload = {
|
||||
[key: string]: string | number
|
||||
[key: string]: string | number | undefined
|
||||
order: string
|
||||
order_by: string
|
||||
per_page: number
|
||||
page: number
|
||||
q?: string
|
||||
}
|
||||
|
||||
export interface IQueryOptions {
|
||||
|
@ -7,8 +7,9 @@ export interface IAppStatistics {
|
||||
|
||||
export type TAppConfig = {
|
||||
[key: string]: number | boolean | string
|
||||
federation_enabled: boolean
|
||||
admin_contact: string
|
||||
gpx_limit_import: number
|
||||
is_email_sending_enabled: boolean
|
||||
is_registration_enabled: boolean
|
||||
map_attribution: string
|
||||
max_single_file_size: number
|
||||
@ -23,7 +24,8 @@ export interface IApplication {
|
||||
}
|
||||
|
||||
export type TAppConfigForm = {
|
||||
[key: string]: number
|
||||
[key: string]: number | string
|
||||
admin_contact: string
|
||||
gpx_limit_import: number
|
||||
max_single_file_size: number
|
||||
max_users: number
|
||||
|
@ -8,9 +8,9 @@ export interface IUserProfile {
|
||||
birth_date: string | null
|
||||
created_at: string
|
||||
email: string
|
||||
email_to_confirm?: string
|
||||
is_active: boolean
|
||||
first_name: string | null
|
||||
imperial_units: boolean
|
||||
language: string | null
|
||||
last_name: string | null
|
||||
location: string | null
|
||||
nb_sports: number
|
||||
@ -18,10 +18,15 @@ export interface IUserProfile {
|
||||
picture: string | boolean
|
||||
records: IRecord[]
|
||||
sports_list: number[]
|
||||
timezone: string
|
||||
total_distance: number
|
||||
total_duration: string
|
||||
username: string
|
||||
}
|
||||
|
||||
export interface IAuthUserProfile extends IUserProfile {
|
||||
imperial_units: boolean
|
||||
language: string | null
|
||||
timezone: string
|
||||
weekm: boolean
|
||||
}
|
||||
|
||||
@ -31,13 +36,25 @@ export interface IUserPayload {
|
||||
first_name: string
|
||||
last_name: string
|
||||
location: string
|
||||
}
|
||||
|
||||
export interface IUserAccountPayload {
|
||||
email: string
|
||||
password: string
|
||||
password_conf: string
|
||||
new_password?: string
|
||||
}
|
||||
|
||||
export interface IUserAccountUpdatePayload {
|
||||
token: LocationQueryValue | LocationQueryValue[]
|
||||
refreshUser?: boolean
|
||||
}
|
||||
|
||||
export interface IAdminUserPayload {
|
||||
username: string
|
||||
admin: boolean
|
||||
admin?: boolean
|
||||
resetPassword?: boolean
|
||||
activate?: boolean
|
||||
new_email?: string
|
||||
}
|
||||
|
||||
export interface IUserPreferencesPayload {
|
||||
@ -58,13 +75,12 @@ export interface IUserPicturePayload {
|
||||
picture: File
|
||||
}
|
||||
|
||||
export interface IUserPasswordPayload {
|
||||
export interface IUserEmailPayload {
|
||||
email: string
|
||||
}
|
||||
|
||||
export interface IUserPasswordResetPayload {
|
||||
password: string
|
||||
password_conf: string
|
||||
token: string
|
||||
}
|
||||
|
||||
@ -77,7 +93,7 @@ export interface ILoginRegisterFormData {
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
password_conf: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
export interface ILoginOrRegisterData {
|
||||
|
@ -16,6 +16,13 @@ export interface IWorkoutSegment {
|
||||
workout_id: string
|
||||
}
|
||||
|
||||
export interface ICardRecord {
|
||||
id: number
|
||||
value: number | string
|
||||
workout_date: string
|
||||
workout_id: string
|
||||
label: string
|
||||
}
|
||||
export interface IRecord {
|
||||
id: number
|
||||
record_type: string
|
||||
|
@ -45,6 +45,11 @@ export const getQuery = (
|
||||
orderByList,
|
||||
defaultOrderBy
|
||||
)
|
||||
if (typeof locationQuery.q === 'string') {
|
||||
query.q = locationQuery.q
|
||||
} else {
|
||||
delete query.q
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
/* eslint-disable import/no-duplicates */
|
||||
import { Locale } from 'date-fns'
|
||||
import { enUS, fr } from 'date-fns/locale'
|
||||
import { de, enUS, fr } from 'date-fns/locale'
|
||||
|
||||
import createI18n from '@/i18n'
|
||||
|
||||
export const localeFromLanguage: Record<string, Locale> = {
|
||||
de: de,
|
||||
en: enUS,
|
||||
fr: fr,
|
||||
}
|
||||
|
||||
export const languageLabels: Record<string, string> = {
|
||||
de: 'Deutsch',
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
}
|
||||
|
39
fittrackee_client/src/utils/password.ts
Normal file
39
fittrackee_client/src/utils/password.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { zxcvbnOptions } from '@zxcvbn-ts/core'
|
||||
|
||||
export const setZxcvbnOptions = async (language: string) => {
|
||||
const zxcvbnCommonPackage = await import(
|
||||
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-common'
|
||||
)
|
||||
const zxcvbnEnPackage = await import(
|
||||
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-en'
|
||||
)
|
||||
const zxcvbnFrPackage = await import(
|
||||
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-fr'
|
||||
)
|
||||
const zxcvbnLangPackages: Record<string, typeof zxcvbnEnPackage> = {
|
||||
en: zxcvbnEnPackage,
|
||||
fr: zxcvbnFrPackage,
|
||||
}
|
||||
const zxcvbnPackage = zxcvbnLangPackages[language]
|
||||
const options = {
|
||||
graphs: zxcvbnCommonPackage.default.adjacencyGraphs,
|
||||
dictionary: {
|
||||
...zxcvbnCommonPackage.default.dictionary,
|
||||
...zxcvbnPackage.default.dictionary,
|
||||
},
|
||||
}
|
||||
zxcvbnOptions.setOptions(options)
|
||||
}
|
||||
|
||||
export const getPasswordStrength = (strength: number): string => {
|
||||
switch (strength) {
|
||||
case 2:
|
||||
return 'AVERAGE'
|
||||
case 3:
|
||||
return 'GOOD'
|
||||
case 4:
|
||||
return 'STRONG'
|
||||
default:
|
||||
return 'WEAK'
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user