Client - init profile edition
This commit is contained in:
parent
fa164345d5
commit
5b470b7786
@ -44,7 +44,7 @@
|
||||
type="password"
|
||||
required
|
||||
v-model="formData.password_conf"
|
||||
:placeholder="t('user.PASSWORD-CONFIRM')"
|
||||
:placeholder="t('user.PASSWORD_CONFIRM')"
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" :disabled="registration_disabled">
|
||||
|
@ -64,7 +64,9 @@
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="profile-buttons">
|
||||
<button>{{ t('user.PROFILE.EDIT') }}</button>
|
||||
<button @click="$router.push('/profile/edit')">
|
||||
{{ t('user.PROFILE.EDIT') }}
|
||||
</button>
|
||||
<button @click="$router.push('/')">{{ t('common.HOME') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
294
fittrackee_client/src/components/User/ProfileEdition.vue
Normal file
294
fittrackee_client/src/components/User/ProfileEdition.vue
Normal file
@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div id="user-profile-edition">
|
||||
<Card>
|
||||
<template #title>{{ t('user.PROFILE.EDITION') }}</template>
|
||||
<template #content>
|
||||
<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
|
||||
id="first_name"
|
||||
v-model="userForm.first_name"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items" for="last_name">
|
||||
{{ t('user.PROFILE.LAST_NAME') }}
|
||||
<input id="last_name" v-model="userForm.last_name" />
|
||||
</label>
|
||||
<label class="form-items" for="birth_date">
|
||||
{{ t('user.PROFILE.BIRTH_DATE') }}
|
||||
<input
|
||||
id="birth_date"
|
||||
type="date"
|
||||
class="birth-date"
|
||||
v-model="userForm.birth_date"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items" for="location">
|
||||
{{ t('user.PROFILE.LOCATION') }}
|
||||
<input
|
||||
id="location"
|
||||
v-model="userForm.location"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items">
|
||||
{{ t('user.PROFILE.BIO') }}
|
||||
<CustomTextArea
|
||||
name="bio"
|
||||
:charLimit="200"
|
||||
:input="userForm.bio"
|
||||
:disabled="loading"
|
||||
@updateValue="updateBio"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items">
|
||||
{{ t('user.PROFILE.LANGUAGE') }}
|
||||
<select
|
||||
id="language"
|
||||
v-model="userForm.language"
|
||||
:disabled="loading"
|
||||
>
|
||||
<option
|
||||
v-for="lang in availableLanguages"
|
||||
:value="lang.value"
|
||||
:key="lang.value"
|
||||
>
|
||||
{{ lang.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-items" for="timezone">
|
||||
{{ t('user.PROFILE.TIMEZONE') }}
|
||||
<input
|
||||
id="timezone"
|
||||
v-model="userForm.timezone"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items">
|
||||
{{ t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
|
||||
<select id="weekm" v-model="userForm.weekm" :disabled="loading">
|
||||
<option
|
||||
v-for="start in weekStart"
|
||||
:value="start.value"
|
||||
:key="start.value"
|
||||
>
|
||||
{{ t(`user.PROFILE.${start.label}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="form-buttons">
|
||||
<button class="confirm" type="submit">
|
||||
{{ t('buttons.SUBMIT') }}
|
||||
</button>
|
||||
<button class="cancel" @click.prevent="$router.go(-1)">
|
||||
{{ t('buttons.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import CustomTextArea from '@/components/Common/CustomTextArea.vue'
|
||||
import ErrorMessage from '@/components/Common/ErrorMessage.vue'
|
||||
import { ROOT_STORE, USER_STORE } from '@/store/constants'
|
||||
import { IAuthUserProfile, IUserPayload } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileEdition',
|
||||
components: {
|
||||
Card,
|
||||
CustomTextArea,
|
||||
ErrorMessage,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IAuthUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t, availableLocales } = useI18n()
|
||||
const store = useStore()
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
location: '',
|
||||
bio: '',
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const availableLanguages = availableLocales.map((l) => {
|
||||
return { label: l.toUpperCase(), value: l }
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IAuthUserProfile) {
|
||||
userForm.first_name = user.first_name ? user.first_name : ''
|
||||
userForm.last_name = user.last_name ? user.last_name : ''
|
||||
userForm.birth_date = user.birth_date
|
||||
? format(new Date(user.birth_date), 'yyyy-MM-dd')
|
||||
: ''
|
||||
userForm.location = user.location ? user.location : ''
|
||||
userForm.bio = user.bio ? user.bio : ''
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
userForm.weekm = user.weekm ? user.weekm : false
|
||||
}
|
||||
function updateBio(value: string) {
|
||||
userForm.bio = value
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
|
||||
return {
|
||||
availableLanguages,
|
||||
errorMessages,
|
||||
loading,
|
||||
registrationDate,
|
||||
t,
|
||||
userForm,
|
||||
weekStart,
|
||||
updateBio,
|
||||
updateProfile,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
|
||||
#user-profile-edition {
|
||||
margin: auto;
|
||||
width: 700px;
|
||||
@media screen and (max-width: $medium-limit) {
|
||||
width: 100%;
|
||||
margin: 0 auto 50px auto;
|
||||
}
|
||||
|
||||
.profile-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
hr {
|
||||
border-color: var(--card-border-color);
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
|
||||
.form-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
input {
|
||||
margin: $default-padding * 0.5 0;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 35px;
|
||||
padding: $default-padding * 0.5 0;
|
||||
}
|
||||
::v-deep(.custom-textarea) {
|
||||
textarea {
|
||||
padding: $default-padding * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $default-padding;
|
||||
}
|
||||
.birth-date {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
padding: $default-padding 0;
|
||||
gap: $default-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,15 +4,23 @@
|
||||
"LOGIN": "Login",
|
||||
"LOGOUT": "Logout",
|
||||
"PASSWORD": "Password",
|
||||
"PASSWORD-CONFIRM": "Confirm Password",
|
||||
"PASSWORD_CONFIRM": "Confirm Password",
|
||||
"PASSWORD_CONFIRMATION": "Password confirmation",
|
||||
"PROFILE": {
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Birth date",
|
||||
"EDIT": "Edit profile",
|
||||
"EDITION": "Profile edition",
|
||||
"FIRST_NAME": "First name",
|
||||
"FIRST_DAY_OF_WEEK": "First day of week",
|
||||
"LANGUAGE": "Language",
|
||||
"LAST_NAME": "Last name",
|
||||
"LOCATION": "Location",
|
||||
"REGISTRATION_DATE": "Registration date"
|
||||
"MONDAY": "Monday",
|
||||
"PICTURE": "Picture",
|
||||
"REGISTRATION_DATE": "Registration date",
|
||||
"SUNDAY": "Sunday",
|
||||
"TIMEZONE": "Timezone"
|
||||
},
|
||||
"REGISTER": "Register",
|
||||
"REGISTER_DISABLED": "Sorry, registration is disabled.",
|
||||
|
@ -5,15 +5,23 @@
|
||||
"LOGIN": "Se connecter",
|
||||
"LOGOUT": "Se déconnecter",
|
||||
"PASSWORD": "Mot de passe",
|
||||
"PASSWORD-CONFIRM": "Confirmation du mot de passe",
|
||||
"PASSWORD_CONFIRM": "Confirmation du mot de passe",
|
||||
"PASSWORD_CONFIRMATION": "Confirmation du mot de passe",
|
||||
"PROFILE": {
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Date de naissance",
|
||||
"EDIT": "Modifier le profil",
|
||||
"EDITION": "Mise à jour du profil",
|
||||
"FIRST_DAY_OF_WEEK": "Premier jour de la semaine",
|
||||
"FIRST_NAME": "Prénom",
|
||||
"LANGUAGE": "Langue",
|
||||
"LAST_NAME": "Nom",
|
||||
"LOCATION": "Lieu",
|
||||
"REGISTRATION_DATE": "Date d'inscription"
|
||||
"MONDAY": "Lundi",
|
||||
"PICTURE": "Avatar",
|
||||
"REGISTRATION_DATE": "Date d'inscription",
|
||||
"SUNDAY": "Dimanche",
|
||||
"TIMEZONE": "Fuseau horaire"
|
||||
},
|
||||
"REGISTER": "S'inscrire",
|
||||
"REGISTER_DISABLED": "Désolé, les inscriptions sont désactivées.",
|
||||
|
@ -34,6 +34,13 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: ProfileView,
|
||||
props: { edition: false },
|
||||
},
|
||||
{
|
||||
path: '/profile/edit',
|
||||
name: 'ProfileEdition',
|
||||
component: ProfileView,
|
||||
props: { edition: true },
|
||||
},
|
||||
{
|
||||
path: '/statistics',
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '@/store/constants'
|
||||
import { IRootState } from '@/store/modules/root/types'
|
||||
import { IUserActions, IUserState } from '@/store/modules/user/types'
|
||||
import { ILoginOrRegisterData } from '@/types/user'
|
||||
import { ILoginOrRegisterData, IUserPayload } from '@/types/user'
|
||||
import { handleError } from '@/utils'
|
||||
|
||||
export const actions: ActionTree<IUserState, IRootState> & IUserActions = {
|
||||
@ -80,4 +80,28 @@ export const actions: ActionTree<IUserState, IRootState> & IUserActions = {
|
||||
context.commit(WORKOUTS_STORE.MUTATIONS.EMPTY_WORKOUTS)
|
||||
router.push('/login')
|
||||
},
|
||||
[USER_STORE.ACTIONS.UPDATE_USER_PROFILE](
|
||||
context: ActionContext<IUserState, IRootState>,
|
||||
payload: IUserPayload
|
||||
): void {
|
||||
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
context.commit(USER_STORE.MUTATIONS.UPDATE_USER_LOADING, true)
|
||||
authApi
|
||||
.post('auth/profile/edit', payload)
|
||||
.then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(
|
||||
USER_STORE.MUTATIONS.UPDATE_AUTH_USER_PROFILE,
|
||||
res.data.data
|
||||
)
|
||||
router.push('/profile')
|
||||
} else {
|
||||
handleError(context, null)
|
||||
}
|
||||
})
|
||||
.catch((error) => handleError(context, error))
|
||||
.finally(() =>
|
||||
context.commit(USER_STORE.MUTATIONS.UPDATE_USER_LOADING, false)
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -3,16 +3,19 @@ export enum UserActions {
|
||||
GET_USER_PROFILE = 'GET_USER_PROFILE',
|
||||
LOGIN_OR_REGISTER = 'LOGIN_OR_REGISTER',
|
||||
LOGOUT = 'LOGOUT',
|
||||
UPDATE_USER_PROFILE = 'UPDATE_USER_PROFILE',
|
||||
}
|
||||
|
||||
export enum UserGetters {
|
||||
AUTH_TOKEN = 'AUTH_TOKEN',
|
||||
AUTH_USER_PROFILE = 'AUTH_USER_PROFILE',
|
||||
IS_AUTHENTICATED = 'IS_AUTHENTICATED',
|
||||
USER_LOADING = 'USER_LOADING',
|
||||
}
|
||||
|
||||
export enum UserMutations {
|
||||
CLEAR_AUTH_USER_TOKEN = 'CLEAR_AUTH_USER_TOKEN',
|
||||
UPDATE_AUTH_TOKEN = 'UPDATE_AUTH_TOKEN',
|
||||
UPDATE_AUTH_USER_PROFILE = 'UPDATE_AUTH_USER_PROFILE',
|
||||
UPDATE_USER_LOADING = 'UPDATE_USER_LOADING',
|
||||
}
|
||||
|
@ -14,4 +14,7 @@ export const getters: GetterTree<IUserState, IRootState> & IUserGetters = {
|
||||
[USER_STORE.GETTERS.IS_AUTHENTICATED]: (state: IUserState) => {
|
||||
return state.authToken !== null
|
||||
},
|
||||
[USER_STORE.GETTERS.USER_LOADING]: (state: IUserState) => {
|
||||
return state.loading
|
||||
},
|
||||
}
|
||||
|
@ -21,4 +21,10 @@ export const mutations: MutationTree<IUserState> & TUserMutations = {
|
||||
) {
|
||||
state.authUserProfile = authUserProfile
|
||||
},
|
||||
[USER_STORE.MUTATIONS.UPDATE_USER_LOADING](
|
||||
state: IUserState,
|
||||
loading: boolean
|
||||
) {
|
||||
state.loading = loading
|
||||
},
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ import { IAuthUserProfile } from '@/types/user'
|
||||
export const userState: IUserState = {
|
||||
authToken: null,
|
||||
authUserProfile: <IAuthUserProfile>{},
|
||||
loading: false,
|
||||
}
|
||||
|
@ -7,11 +7,16 @@ import {
|
||||
|
||||
import { USER_STORE } from '@/store/constants'
|
||||
import { IRootState } from '@/store/modules/root/types'
|
||||
import { IAuthUserProfile, ILoginOrRegisterData } from '@/types/user'
|
||||
import {
|
||||
IAuthUserProfile,
|
||||
ILoginOrRegisterData,
|
||||
IUserPayload,
|
||||
} from '@/types/user'
|
||||
|
||||
export interface IUserState {
|
||||
authToken: string | null
|
||||
authUserProfile: IAuthUserProfile
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export interface IUserActions {
|
||||
@ -31,6 +36,11 @@ export interface IUserActions {
|
||||
[USER_STORE.ACTIONS.LOGOUT](
|
||||
context: ActionContext<IUserState, IRootState>
|
||||
): void
|
||||
|
||||
[USER_STORE.ACTIONS.UPDATE_USER_PROFILE](
|
||||
context: ActionContext<IUserState, IRootState>,
|
||||
payload: IUserPayload
|
||||
): void
|
||||
}
|
||||
|
||||
export interface IUserGetters {
|
||||
@ -39,6 +49,8 @@ export interface IUserGetters {
|
||||
[USER_STORE.GETTERS.AUTH_USER_PROFILE](state: IUserState): IAuthUserProfile
|
||||
|
||||
[USER_STORE.GETTERS.IS_AUTHENTICATED](state: IUserState): boolean
|
||||
|
||||
[USER_STORE.GETTERS.USER_LOADING](state: IUserState): boolean
|
||||
}
|
||||
|
||||
export type TUserMutations<S = IUserState> = {
|
||||
@ -48,6 +60,7 @@ export type TUserMutations<S = IUserState> = {
|
||||
state: S,
|
||||
authUserProfile: IAuthUserProfile
|
||||
): void
|
||||
[USER_STORE.MUTATIONS.UPDATE_USER_LOADING](state: S, loading: boolean): void
|
||||
}
|
||||
|
||||
export type TUserStoreModule<S = IUserState> = Omit<
|
||||
|
@ -22,6 +22,19 @@ export interface IAuthUserProfile {
|
||||
weekm: boolean
|
||||
}
|
||||
|
||||
export interface IUserPayload {
|
||||
bio: string
|
||||
birth_date: string
|
||||
first_name: string
|
||||
language: string
|
||||
last_name: string
|
||||
location: string
|
||||
timezone: string
|
||||
weekm: boolean
|
||||
password: string
|
||||
password_conf: string
|
||||
}
|
||||
|
||||
export interface ILoginRegisterFormData {
|
||||
username: string
|
||||
email: string
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="profile" class="container">
|
||||
<Profile :user="authUser" v-if="authUser.username" />
|
||||
<div id="profile" class="container" v-if="authUser.username">
|
||||
<ProfileEdition :user="authUser" v-if="edition" />
|
||||
<Profile :user="authUser" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -8,6 +9,7 @@
|
||||
import { computed, ComputedRef, defineComponent } from 'vue'
|
||||
|
||||
import Profile from '@/components/User/Profile.vue'
|
||||
import ProfileEdition from '@/components/User/ProfileEdition.vue'
|
||||
import { USER_STORE } from '@/store/constants'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
@ -16,6 +18,13 @@
|
||||
name: 'ProfileView',
|
||||
components: {
|
||||
Profile,
|
||||
ProfileEdition,
|
||||
},
|
||||
props: {
|
||||
edition: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
|
Loading…
Reference in New Issue
Block a user