Client - add user sports preferences
+ minor refactor
This commit is contained in:
@ -35,6 +35,7 @@
|
||||
<SportImage
|
||||
:title="sport.translatedLabel"
|
||||
:sport-label="sport.label"
|
||||
:color="sport.color"
|
||||
/>
|
||||
</td>
|
||||
<td class="sport-label">
|
||||
@ -127,9 +128,6 @@
|
||||
font-style: italic;
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.sport-action {
|
||||
padding-left: $default-padding * 4;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="sport-img"
|
||||
:style="{ fill: sportColors[sportLabel] }"
|
||||
:style="{ fill: color ? color : sportColors[sportLabel] }"
|
||||
:title="title ? title : $t(`sports.${sportLabel}.LABEL`)"
|
||||
>
|
||||
<CyclingSport v-if="sportLabel === 'Cycling (Sport)'" />
|
||||
@ -37,12 +37,13 @@
|
||||
|
||||
interface Props {
|
||||
sportLabel: string
|
||||
color: string | null
|
||||
title?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
})
|
||||
|
||||
const { sportLabel, title } = toRefs(props)
|
||||
const { color, sportLabel, title } = toRefs(props)
|
||||
const sportColors = inject('sportColors')
|
||||
</script>
|
||||
|
@ -5,7 +5,11 @@
|
||||
$router.push({ name: 'Workout', params: { workoutId: workout.id } })
|
||||
"
|
||||
>
|
||||
<SportImage :sport-label="sportLabel" :title="workout.title" />
|
||||
<SportImage
|
||||
:sport-label="sportLabel"
|
||||
:title="workout.title"
|
||||
:color="sportColor"
|
||||
/>
|
||||
<sup>
|
||||
<i
|
||||
v-if="workout.records.length > 0"
|
||||
@ -28,6 +32,7 @@
|
||||
interface Props {
|
||||
workout: IWorkout
|
||||
sportLabel: string
|
||||
sportColor: string | null
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
:key="index"
|
||||
:workout="workout"
|
||||
:sportLabel="getSportLabel(workout, sports)"
|
||||
:sportColor="getSportColor(workout, sports)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="donut-display">
|
||||
@ -41,7 +42,7 @@
|
||||
import CalendarWorkoutsChart from '@/components/Dashboard/UserCalendar/CalendarWorkoutsChart.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getSportLabel, sportIdColors } from '@/utils/sports'
|
||||
import { getSportColor, getSportLabel, sportIdColors } from '@/utils/sports'
|
||||
import { getDonutDatasets } from '@/utils/workouts'
|
||||
|
||||
interface Props {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="records-card">
|
||||
<Card>
|
||||
<template #title>
|
||||
<SportImage :sport-label="records.label" />
|
||||
<SportImage :sport-label="records.label" :color="records.color" />
|
||||
{{ sportTranslatedLabel }}
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -13,7 +13,7 @@
|
||||
:checked="selectedSportIds.includes(sport.id)"
|
||||
@input="updateSelectedSportIds(sport.id)"
|
||||
/>
|
||||
<SportImage :sport-label="sport.label" />
|
||||
<SportImage :sport-label="sport.label" :color="sport.color" />
|
||||
<span class="sport-label">{{ sport.translatedLabel }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -91,9 +91,5 @@
|
||||
.user-bio {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.profile-buttons {
|
||||
display: flex;
|
||||
gap: $default-padding;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -38,13 +38,3 @@
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
#user-preferences {
|
||||
.profile-buttons {
|
||||
display: flex;
|
||||
gap: $default-padding;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -22,7 +22,7 @@
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PREFERENCES']
|
||||
const tabs = ['PROFILE', 'PREFERENCES', 'SPORTS']
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -34,7 +34,7 @@
|
||||
const store = useStore()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES']
|
||||
const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES', 'SPORTS']
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
|
@ -38,7 +38,10 @@
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
case 'PREFERENCES':
|
||||
return `/profile${props.edition ? '/edit' : ''}/preferences`
|
||||
case 'SPORTS':
|
||||
return `/profile${
|
||||
props.edition ? '/edit' : ''
|
||||
}/${tab.toLocaleLowerCase()}`
|
||||
default:
|
||||
case 'PROFILE':
|
||||
return `/profile${props.edition ? '/edit' : ''}`
|
||||
|
280
fittrackee_client/src/components/User/UserSportPreferences.vue
Normal file
280
fittrackee_client/src/components/User/UserSportPreferences.vue
Normal file
@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div id="user-sport-preferences">
|
||||
<div class="responsive-table" v-if="sports.length > 0">
|
||||
<div class="mobile-display">
|
||||
<div v-if="isEdition" class="profile-buttons mobile-display">
|
||||
<button
|
||||
class="cancel"
|
||||
@click.prevent="$router.push('/profile/sports')"
|
||||
>
|
||||
{{ $t('buttons.BACK') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="profile-buttons">
|
||||
<button @click="$router.push('/profile/edit/sports')">
|
||||
{{ $t('user.PROFILE.EDIT_SPORTS_PREFERENCES') }}
|
||||
</button>
|
||||
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('user.PROFILE.SPORT.COLOR') }}</th>
|
||||
<th class="text-left">{{ $t('workouts.SPORT', 0) }}</th>
|
||||
<th>{{ $t('user.PROFILE.SPORT.IS_ACTIVE') }}</th>
|
||||
<th>{{ $t('user.PROFILE.SPORT.STOPPED_SPEED_THRESHOLD') }}</th>
|
||||
<th v-if="isEdition">{{ $t('user.PROFILE.SPORT.ACTION') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="sport in translatedSports" :key="sport.id">
|
||||
<td>
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.SPORT.COLOR') }}
|
||||
</span>
|
||||
<input
|
||||
v-if="isSportInEdition(sport.id)"
|
||||
class="sport-color"
|
||||
type="color"
|
||||
:value="sportPayload.color"
|
||||
@input="updateColor"
|
||||
/>
|
||||
<SportImage
|
||||
v-else
|
||||
:title="sport.translatedLabel"
|
||||
:sport-label="sport.label"
|
||||
:color="sport.color ? sport.color : sportColors[sport.label]"
|
||||
/>
|
||||
</td>
|
||||
<td class="sport-label">
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.SPORT.LABEL') }}
|
||||
</span>
|
||||
{{ sport.translatedLabel }}
|
||||
<i
|
||||
v-if="loading && isSportInEdition(sport.id)"
|
||||
class="fa fa-refresh fa-spin fa-fw"
|
||||
/>
|
||||
<ErrorMessage
|
||||
:message="errorMessages"
|
||||
v-if="errorMessages && sportPayload.sport_id === sport.id"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.SPORT.IS_ACTIVE') }}
|
||||
</span>
|
||||
<input
|
||||
v-if="isSportInEdition(sport.id)"
|
||||
type="checkbox"
|
||||
:checked="sport.is_active_for_user"
|
||||
@change="updateIsActive"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
:class="`fa fa${
|
||||
sport.is_active_for_user ? '-check' : ''
|
||||
}-square-o`"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.SPORT.STOPPED_SPEED_THRESHOLD') }}
|
||||
</span>
|
||||
<input
|
||||
class="threshold-input"
|
||||
v-if="isSportInEdition(sport.id)"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
:value="sportPayload.stopped_speed_threshold"
|
||||
@input="updateThreshold"
|
||||
/>
|
||||
<span v-else>
|
||||
{{ sport.stopped_speed_threshold }}
|
||||
</span>
|
||||
</td>
|
||||
<td v-if="isEdition" class="action-buttons">
|
||||
<span class="cell-heading">
|
||||
{{ $t('user.PROFILE.SPORT.ACTION') }}
|
||||
</span>
|
||||
<button
|
||||
v-if="sportPayload.sport_id === 0"
|
||||
@click="updateSportInEdition(sport)"
|
||||
>
|
||||
{{ $t('buttons.EDIT') }}
|
||||
</button>
|
||||
<div v-if="isSportInEdition(sport.id)" class="edition-buttons">
|
||||
<button :disabled="loading" @click="updateSport">
|
||||
{{ $t('buttons.SUBMIT') }}
|
||||
</button>
|
||||
<button :disabled="loading" @click="updateSportInEdition(null)">
|
||||
{{ $t('buttons.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="isEdition" class="profile-buttons">
|
||||
<button class="cancel" @click.prevent="$router.push('/profile/sports')">
|
||||
{{ $t('buttons.BACK') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="profile-buttons">
|
||||
<button @click="$router.push('/profile/edit/sports')">
|
||||
{{ $t('user.PROFILE.EDIT_SPORTS_PREFERENCES') }}
|
||||
</button>
|
||||
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, inject, reactive, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { AUTH_USER_STORE, ROOT_STORE, SPORTS_STORE } from '@/store/constants'
|
||||
import { ISport, ITranslatedSport } from '@/types/sports'
|
||||
import { IUserSportPreferencesPayload } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
interface Props {
|
||||
isEdition: boolean
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const store = useStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isEdition } = toRefs(props)
|
||||
const sportColors = inject('sportColors')
|
||||
const sports: ComputedRef<ISport[]> = computed(
|
||||
() => store.getters[SPORTS_STORE.GETTERS.SPORTS]
|
||||
)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(sports.value, t)
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const sportPayload: IUserSportPreferencesPayload = reactive({
|
||||
sport_id: 0,
|
||||
color: null,
|
||||
is_active: true,
|
||||
stopped_speed_threshold: 1,
|
||||
})
|
||||
|
||||
function updateSportInEdition(sport: ISport | null) {
|
||||
if (sport !== null) {
|
||||
sportPayload.sport_id = sport.id
|
||||
sportPayload.color = sport.color ? sport.color : sportColors[sport.label]
|
||||
sportPayload.is_active = sport.is_active_for_user
|
||||
sportPayload.stopped_speed_threshold = sport.stopped_speed_threshold
|
||||
} else {
|
||||
resetSportPayload()
|
||||
}
|
||||
}
|
||||
function isSportInEdition(sportId: number) {
|
||||
return sportPayload.sport_id === sportId
|
||||
}
|
||||
function updateColor(event: Event & { target: HTMLInputElement }) {
|
||||
sportPayload.color = event.target.value
|
||||
}
|
||||
function updateThreshold(event: Event & { target: HTMLInputElement }) {
|
||||
sportPayload.stopped_speed_threshold = parseFloat(event.target.value)
|
||||
}
|
||||
function updateIsActive(event: Event & { target: HTMLInputElement }) {
|
||||
sportPayload.is_active = event.target.checked
|
||||
}
|
||||
function resetSportPayload() {
|
||||
sportPayload.sport_id = 0
|
||||
sportPayload.color = null
|
||||
sportPayload.is_active = true
|
||||
sportPayload.stopped_speed_threshold = 1
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
}
|
||||
function updateSport(event: Event) {
|
||||
event.preventDefault()
|
||||
store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.UPDATE_USER_SPORT_PREFERENCES,
|
||||
sportPayload
|
||||
)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => loading.value,
|
||||
(newIsLoading) => {
|
||||
if (!newIsLoading && !errorMessages.value) {
|
||||
resetSportPayload()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
#user-sport-preferences {
|
||||
.sport-img {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.sport-color {
|
||||
border: none;
|
||||
margin: 6px 1px 6px 0;
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
}
|
||||
.sport-label {
|
||||
width: 170px;
|
||||
}
|
||||
.action-buttons {
|
||||
width: 70px;
|
||||
}
|
||||
.edition-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $default-padding * 0.5;
|
||||
line-height: 1.3em;
|
||||
|
||||
button {
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
.threshold-input {
|
||||
padding: $default-padding * 0.5;
|
||||
width: 50px;
|
||||
}
|
||||
.mobile-display {
|
||||
display: none;
|
||||
}
|
||||
div.error-message {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
.sport-label {
|
||||
width: 100%;
|
||||
}
|
||||
.action-buttons {
|
||||
width: 100%;
|
||||
}
|
||||
.edition-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
.mobile-display {
|
||||
display: flex;
|
||||
margin: $default-margin * 2 0 $default-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -75,7 +75,11 @@
|
||||
"
|
||||
>
|
||||
<div class="img">
|
||||
<SportImage v-if="sport.label" :sport-label="sport.label" />
|
||||
<SportImage
|
||||
v-if="sport.label"
|
||||
:sport-label="sport.label"
|
||||
:color="sport.color"
|
||||
/>
|
||||
</div>
|
||||
<div class="data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
|
@ -17,7 +17,7 @@
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="workout-card-title">
|
||||
<SportImage :sport-label="sport.label" />
|
||||
<SportImage :sport-label="sport.label" :color="sport.color" />
|
||||
<div class="workout-title-date">
|
||||
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
|
||||
{{ workoutObject.title }}
|
||||
|
@ -46,7 +46,7 @@
|
||||
v-model="workoutForm.sport_id"
|
||||
>
|
||||
<option
|
||||
v-for="sport in translatedSports.filter((s) => s.is_active)"
|
||||
v-for="sport in translatedSports"
|
||||
:value="sport.id"
|
||||
:key="sport.id"
|
||||
>
|
||||
@ -259,7 +259,7 @@
|
||||
|
||||
const { workout, isCreation, loading } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
translateSports(props.sports, t, true)
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
|
@ -52,6 +52,9 @@
|
||||
:sport-label="
|
||||
sports.filter((s) => s.id === workout.sport_id)[0].label
|
||||
"
|
||||
:color="
|
||||
sports.filter((s) => s.id === workout.sport_id)[0].color
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
|
@ -23,6 +23,7 @@
|
||||
"BIRTH_DATE": "Birth date",
|
||||
"EDIT": "Edit profile",
|
||||
"EDIT_PREFERENCES": "Edit preferences",
|
||||
"EDIT_SPORTS_PREFERENCES": "Edit sports preferences",
|
||||
"FIRST_NAME": "First name",
|
||||
"FIRST_DAY_OF_WEEK": "First day of week",
|
||||
"LANGUAGE": "Language",
|
||||
@ -36,11 +37,20 @@
|
||||
"PREFERENCES_EDITION": "Preferences edition",
|
||||
"PROFILE_EDITION": "Profile edition",
|
||||
"REGISTRATION_DATE": "Registration date",
|
||||
"SPORTS_EDITION": "Sports preferences edition",
|
||||
"SUNDAY": "Sunday",
|
||||
"TABS": {
|
||||
"PICTURE": "picture",
|
||||
"PREFERENCES": "preferences",
|
||||
"PROFILE": "profile"
|
||||
"PROFILE": "profile",
|
||||
"SPORTS": "sports"
|
||||
},
|
||||
"SPORT": {
|
||||
"ACTION": "action",
|
||||
"COLOR": "color",
|
||||
"IS_ACTIVE": "active",
|
||||
"LABEL": "label",
|
||||
"STOPPED_SPEED_THRESHOLD": "stopped speed threshold"
|
||||
},
|
||||
"TIMEZONE": "Timezone"
|
||||
},
|
||||
|
@ -23,6 +23,7 @@
|
||||
"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",
|
||||
"LANGUAGE": "Langue",
|
||||
@ -36,11 +37,20 @@
|
||||
"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"
|
||||
"PROFILE": "profil",
|
||||
"SPORTS": "sports"
|
||||
},
|
||||
"SPORT": {
|
||||
"ACTION": "action",
|
||||
"COLOR": "couleur",
|
||||
"IS_ACTIVE": "actif",
|
||||
"LABEL": "label",
|
||||
"STOPPED_SPEED_THRESHOLD": "seuil de vitesse arrêtée"
|
||||
},
|
||||
"TIMEZONE": "Fuseau horaire"
|
||||
},
|
||||
@ -49,4 +59,4 @@
|
||||
"RESET_PASSWORD": "Réinitialiser votre mot de passe",
|
||||
"USER_PICTURE": "photo de l'utilisateur",
|
||||
"USERNAME": "Nom d'utilisateur"
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import ProfileEdition from '@/components/User/ProfileEdition/index.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'
|
||||
|
||||
@ -101,6 +102,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'UserPreferences',
|
||||
component: UserPreferences,
|
||||
},
|
||||
{
|
||||
path: 'sports',
|
||||
name: 'UserSportPreferences',
|
||||
component: UserSportPreferences,
|
||||
props: { isEdition: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -126,6 +133,12 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'UserPreferencesEdition',
|
||||
component: UserPreferencesEdition,
|
||||
},
|
||||
{
|
||||
path: 'sports',
|
||||
name: 'UserSportPreferencesEdition',
|
||||
component: UserSportPreferences,
|
||||
props: { isEdition: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -254,9 +254,12 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.center-text {
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.responsive-table {
|
||||
margin-bottom: 15px;
|
||||
@ -337,3 +340,14 @@ button {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-buttons {
|
||||
display: flex;
|
||||
gap: $default-padding;
|
||||
}
|
||||
|
||||
.medium-sport-img {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
margin: 0 auto;
|
||||
}
|
@ -25,6 +25,7 @@ import {
|
||||
IUserPayload,
|
||||
IUserPicturePayload,
|
||||
IUserPreferencesPayload,
|
||||
IUserSportPreferencesPayload,
|
||||
} from '@/types/user'
|
||||
import { handleError } from '@/utils'
|
||||
|
||||
@ -172,6 +173,26 @@ export const actions: ActionTree<IAuthUserState, IRootState> &
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
)
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_SPORT_PREFERENCES](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserSportPreferencesPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, true)
|
||||
authApi
|
||||
.post('auth/profile/edit/sports', payload)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.dispatch(SPORTS_STORE.ACTIONS.GET_SPORTS)
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
handleError(context, error)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
})
|
||||
},
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPicturePayload
|
||||
|
@ -10,6 +10,7 @@ export enum AuthUserActions {
|
||||
UPDATE_USER_PICTURE = 'UPDATE_USER_PICTURE',
|
||||
UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE',
|
||||
UPDATE_USER_PREFERENCES = 'UPDATE_USER_PREFERENCES',
|
||||
UPDATE_USER_SPORT_PREFERENCES = 'UPDATE_USER_SPORT_PREFERENCES',
|
||||
}
|
||||
|
||||
export enum AuthUserGetters {
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
IUserPayload,
|
||||
IUserPicturePayload,
|
||||
IUserPreferencesPayload,
|
||||
IUserSportPreferencesPayload,
|
||||
} from '@/types/user'
|
||||
|
||||
export interface IAuthUserState {
|
||||
@ -52,6 +53,11 @@ export interface IAuthUserActions {
|
||||
payload: IUserPreferencesPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_SPORT_PREFERENCES](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserSportPreferencesPayload
|
||||
): void
|
||||
|
||||
[AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE](
|
||||
context: ActionContext<IAuthUserState, IRootState>,
|
||||
payload: IUserPicturePayload
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
|
||||
import authApi from '@/api/authApi'
|
||||
import { ROOT_STORE, SPORTS_STORE } from '@/store/constants'
|
||||
import { AUTH_USER_STORE, ROOT_STORE, SPORTS_STORE } from '@/store/constants'
|
||||
import { IRootState } from '@/store/modules/root/types'
|
||||
import { ISportsActions, ISportsState } from '@/store/modules/sports/types'
|
||||
import { ISportPayload } from '@/types/sports'
|
||||
@ -20,6 +20,7 @@ export const actions: ActionTree<ISportsState, IRootState> & ISportsActions = {
|
||||
SPORTS_STORE.MUTATIONS.SET_SPORTS,
|
||||
res.data.data.sports
|
||||
)
|
||||
context.commit(AUTH_USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
export interface ISport {
|
||||
color: string | null
|
||||
has_workouts: boolean
|
||||
id: number
|
||||
img: string
|
||||
is_active: boolean
|
||||
is_active_for_user: boolean
|
||||
label: string
|
||||
stopped_speed_threshold: number
|
||||
}
|
||||
|
||||
export interface ITranslatedSport extends ISport {
|
||||
|
@ -45,6 +45,13 @@ export interface IUserPreferencesPayload {
|
||||
weekm: boolean
|
||||
}
|
||||
|
||||
export interface IUserSportPreferencesPayload {
|
||||
sport_id: number
|
||||
color: string | null
|
||||
is_active: boolean
|
||||
stopped_speed_threshold: number
|
||||
}
|
||||
|
||||
export interface IUserPicturePayload {
|
||||
picture: File
|
||||
}
|
||||
|
@ -27,8 +27,9 @@ export interface IRecord {
|
||||
}
|
||||
|
||||
export interface IRecordsBySport {
|
||||
[key: string]: string | Record<string, string | number>[]
|
||||
[key: string]: string | Record<string, string | number>[] | null
|
||||
label: string
|
||||
color: string | null
|
||||
records: Record<string, string | number>[]
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ export const getRecordsBySports = (
|
||||
if (sportList[sport.translatedLabel] === void 0) {
|
||||
sportList[sport.translatedLabel] = {
|
||||
label: sport.label,
|
||||
color: sport.color,
|
||||
records: [],
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export const translateSports = (
|
||||
onlyActive = false
|
||||
): ITranslatedSport[] =>
|
||||
sports
|
||||
.filter((sport) => (onlyActive ? sport.is_active : true))
|
||||
.filter((sport) => (onlyActive ? sport.is_active_for_user : true))
|
||||
.map((sport) => ({
|
||||
...sport,
|
||||
translatedLabel: t(`sports.${sport.label}.LABEL`),
|
||||
@ -50,3 +50,12 @@ export const getSportLabel = (workout: IWorkout, sports: ISport[]): string => {
|
||||
.filter((sport) => sport.id === workout.sport_id)
|
||||
.map((sport) => sport.label)[0]
|
||||
}
|
||||
|
||||
export const getSportColor = (
|
||||
workout: IWorkout,
|
||||
sports: ISport[]
|
||||
): string | null => {
|
||||
return sports
|
||||
.filter((sport) => sport.id === workout.sport_id)
|
||||
.map((sport) => sport.color)[0]
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="profile" class="container view" v-if="authUser.username">
|
||||
<router-view :user="authUser"></router-view>
|
||||
<div id="bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
Reference in New Issue
Block a user