Client - add link to users in admin

This commit is contained in:
Sam 2021-10-31 18:11:18 +01:00
parent 332983d9ea
commit fca417d9ad
15 changed files with 218 additions and 5 deletions

View File

@ -41,7 +41,9 @@
<span class="cell-heading"> <span class="cell-heading">
{{ $t('user.USERNAME') }} {{ $t('user.USERNAME') }}
</span> </span>
{{ user.username }} <router-link :to="`/users/${user.username}`">
{{ user.username }}
</router-link>
</td> </td>
<td> <td>
<span class="cell-heading"> <span class="cell-heading">
@ -116,6 +118,7 @@
watch, watch,
capitalize, capitalize,
onBeforeMount, onBeforeMount,
onUnmounted,
} from 'vue' } from 'vue'
import { LocationQuery, useRoute, useRouter } from 'vue-router' import { LocationQuery, useRoute, useRouter } from 'vue-router'
@ -217,6 +220,10 @@
} }
) )
onUnmounted(() => {
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
})
return { return {
authUser, authUser,
errorMessages, errorMessages,

View File

@ -6,7 +6,12 @@
{{ title }} {{ title }}
</template> </template>
<template #content> <template #content>
<div class="modal-message">{{ message }}</div> <div class="modal-message" v-if="strongMessage">
<i18n-t :keypath="message">
<span>{{ strongMessage }}</span>
</i18n-t>
</div>
<div class="modal-message" v-else>{{ message }}</div>
<ErrorMessage :message="errorMessages" v-if="errorMessages" /> <ErrorMessage :message="errorMessages" v-if="errorMessages" />
<div class="modal-buttons"> <div class="modal-buttons">
<button class="confirm" @click="emit('confirmAction')"> <button class="confirm" @click="emit('confirmAction')">
@ -39,6 +44,10 @@
type: String, type: String,
required: true, required: true,
}, },
strongMessage: {
type: String || null,
default: null,
},
}, },
emits: ['cancelAction', 'confirmAction'], emits: ['cancelAction', 'confirmAction'],
setup(props, { emit }) { setup(props, { emit }) {
@ -90,6 +99,9 @@
.modal-message { .modal-message {
padding: $default-padding; padding: $default-padding;
span {
font-weight: bold;
}
} }
.modal-buttons { .modal-buttons {

View File

@ -1,5 +1,13 @@
<template> <template>
<div id="user-infos" class="description-list"> <div id="user-infos" class="description-list">
<Modal
v-if="displayModal"
:title="$t('common.CONFIRMATION')"
message="admin.CONFIRM_USER_ACCOUNT_DELETION"
:strongMessage="user.username"
@confirmAction="deleteUserAccount(user.username)"
@cancelAction="updateDisplayModal(false)"
/>
<dl> <dl>
<dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt> <dt>{{ $t('user.PROFILE.REGISTRATION_DATE') }}:</dt>
<dd>{{ registrationDate }}</dd> <dd>{{ registrationDate }}</dd>
@ -16,7 +24,17 @@
{{ user.bio }} {{ user.bio }}
</dd> </dd>
</dl> </dl>
<div class="profile-buttons"> <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>
<div class="profile-buttons" v-else>
<button @click="$router.push('/profile/edit')"> <button @click="$router.push('/profile/edit')">
{{ $t('user.PROFILE.EDIT') }} {{ $t('user.PROFILE.EDIT') }}
</button> </button>
@ -27,9 +45,18 @@
<script lang="ts"> <script lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import { PropType, computed, defineComponent } from 'vue' import {
ComputedRef,
PropType,
Ref,
computed,
defineComponent,
ref,
} from 'vue'
import { USER_STORE } from '@/store/constants'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
export default defineComponent({ export default defineComponent({
name: 'UserInfos', name: 'UserInfos',
@ -38,8 +65,16 @@
type: Object as PropType<IUserProfile>, type: Object as PropType<IUserProfile>,
required: true, required: true,
}, },
fromAdmin: {
type: Boolean,
default: false,
},
}, },
setup(props) { setup(props) {
const store = useStore()
const authUser: ComputedRef<IUserProfile> = computed(
() => store.getters[USER_STORE.GETTERS.AUTH_USER_PROFILE]
)
const registrationDate = computed(() => const registrationDate = computed(() =>
props.user.created_at props.user.created_at
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm') ? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
@ -50,7 +85,23 @@
? format(new Date(props.user.birth_date), 'dd/MM/yyyy') ? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
: '' : ''
) )
return { birthDate, registrationDate } let displayModal: Ref<boolean> = ref(false)
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
function deleteUserAccount(username: string) {
store.dispatch(USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
}
return {
authUser,
birthDate,
displayModal,
registrationDate,
deleteUserAccount,
updateDisplayModal,
}
}, },
}) })
</script> </script>

View File

@ -13,6 +13,8 @@
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)" "ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)"
}, },
"BACK_TO_ADMIN": "Back to admin", "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.",
"DELETE_USER": "Delete user",
"ENABLE_DISABLE_SPORTS": "Enable/disable sports.", "ENABLE_DISABLE_SPORTS": "Enable/disable sports.",
"REGISTRATION_DISABLED": "Registration is currently disabled.", "REGISTRATION_DISABLED": "Registration is currently disabled.",
"REGISTRATION_ENABLED": "Registration is currently enabled.", "REGISTRATION_ENABLED": "Registration is currently enabled.",

View File

@ -1,4 +1,5 @@
{ {
"BACK": "Back",
"CANCEL": "Cancel", "CANCEL": "Cancel",
"DELETE_MY_ACCOUNT": "Delete my account", "DELETE_MY_ACCOUNT": "Delete my account",
"DISABLE": "Disable", "DISABLE": "Disable",

View File

@ -13,6 +13,8 @@
"ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip " "ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip "
}, },
"BACK_TO_ADMIN": "Revenir à l'admin", "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.",
"DELETE_USER": "Supprimer l'utilisateur",
"ENABLE_DISABLE_SPORTS": "Activer/désactiver des sports.", "ENABLE_DISABLE_SPORTS": "Activer/désactiver des sports.",
"REGISTRATION_DISABLED": "Les inscriptions sont actuellement désactivées.", "REGISTRATION_DISABLED": "Les inscriptions sont actuellement désactivées.",
"REGISTRATION_ENABLED": "Les inscriptions sont actuellement activées.", "REGISTRATION_ENABLED": "Les inscriptions sont actuellement activées.",

View File

@ -1,4 +1,5 @@
{ {
"BACK": "Précédent",
"CANCEL": "Annuler", "CANCEL": "Annuler",
"DELETE_MY_ACCOUNT": "Supprimer mon compte", "DELETE_MY_ACCOUNT": "Supprimer mon compte",
"DISABLE": "Désactiver", "DISABLE": "Désactiver",

View File

@ -128,6 +128,12 @@ const routes: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: 'main' */ '@/views/StatisticsView.vue'), import(/* webpackChunkName: 'main' */ '@/views/StatisticsView.vue'),
}, },
{
path: '/users/:username',
name: 'User',
component: () =>
import(/* webpackChunkName: 'profile' */ '@/views/UserView.vue'),
},
{ {
path: '/workouts', path: '/workouts',
name: 'Workouts', name: 'Workouts',

View File

@ -9,6 +9,42 @@ import { IAdminUserPayload } from '@/types/user'
import { handleError } from '@/utils' import { handleError } from '@/utils'
export const actions: ActionTree<IUsersState, IRootState> & IUsersActions = { export const actions: ActionTree<IUsersState, IRootState> & IUsersActions = {
[USERS_STORE.ACTIONS.EMPTY_USER](
context: ActionContext<IUsersState, IRootState>
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
context.commit(USERS_STORE.MUTATIONS.UPDATE_USER, {})
},
[USERS_STORE.ACTIONS.EMPTY_USERS](
context: ActionContext<IUsersState, IRootState>
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
context.commit(USERS_STORE.MUTATIONS.UPDATE_USERS, [])
context.commit(USERS_STORE.MUTATIONS.UPDATE_USERS_PAGINATION, {})
},
[USERS_STORE.ACTIONS.GET_USER](
context: ActionContext<IUsersState, IRootState>,
username: string
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
context.commit(USERS_STORE.MUTATIONS.UPDATE_USERS_LOADING, true)
authApi
.get(`users/${username}`)
.then((res) => {
if (res.data.status === 'success') {
context.commit(
USERS_STORE.MUTATIONS.UPDATE_USER,
res.data.data.users[0]
)
} else {
handleError(context, null)
}
})
.catch((error) => handleError(context, error))
.finally(() =>
context.commit(USERS_STORE.MUTATIONS.UPDATE_USERS_LOADING, false)
)
},
[USERS_STORE.ACTIONS.GET_USERS]( [USERS_STORE.ACTIONS.GET_USERS](
context: ActionContext<IUsersState, IRootState>, context: ActionContext<IUsersState, IRootState>,
payload: TPaginationPayload payload: TPaginationPayload

View File

@ -1,15 +1,20 @@
export enum UsersActions { export enum UsersActions {
EMPTY_USER = 'EMPTY_USER',
EMPTY_USERS = 'EMPTY_USERS',
GET_USER = 'GET_USER',
GET_USERS = 'GET_USERS', GET_USERS = 'GET_USERS',
UPDATE_USER = 'UPDATE_USER', UPDATE_USER = 'UPDATE_USER',
} }
export enum UsersGetters { export enum UsersGetters {
USER = 'USER',
USERS = 'USERS', USERS = 'USERS',
USERS_LOADING = 'USERS_LOADING', USERS_LOADING = 'USERS_LOADING',
USERS_PAGINATION = 'USERS_PAGINATION', USERS_PAGINATION = 'USERS_PAGINATION',
} }
export enum UsersMutations { export enum UsersMutations {
UPDATE_USER = 'UPDATE_USER',
UPDATE_USER_IN_USERS = 'UPDATE_USER_IN_USERS', UPDATE_USER_IN_USERS = 'UPDATE_USER_IN_USERS',
UPDATE_USERS = 'UPDATE_USERS', UPDATE_USERS = 'UPDATE_USERS',
UPDATE_USERS_LOADING = 'UPDATE_USERS_LOADING', UPDATE_USERS_LOADING = 'UPDATE_USERS_LOADING',

View File

@ -5,6 +5,9 @@ import { IRootState } from '@/store/modules/root/types'
import { IUsersGetters, IUsersState } from '@/store/modules/users/types' import { IUsersGetters, IUsersState } from '@/store/modules/users/types'
export const getters: GetterTree<IUsersState, IRootState> & IUsersGetters = { export const getters: GetterTree<IUsersState, IRootState> & IUsersGetters = {
[USERS_STORE.GETTERS.USER]: (state: IUsersState) => {
return state.user
},
[USERS_STORE.GETTERS.USERS]: (state: IUsersState) => { [USERS_STORE.GETTERS.USERS]: (state: IUsersState) => {
return state.users return state.users
}, },

View File

@ -6,6 +6,9 @@ import { IPagination } from '@/types/api'
import { IUserProfile } from '@/types/user' import { IUserProfile } from '@/types/user'
export const mutations: MutationTree<IUsersState> & TUsersMutations = { export const mutations: MutationTree<IUsersState> & TUsersMutations = {
[USERS_STORE.MUTATIONS.UPDATE_USER](state: IUsersState, user: IUserProfile) {
state.user = user
},
[USERS_STORE.MUTATIONS.UPDATE_USER_IN_USERS]( [USERS_STORE.MUTATIONS.UPDATE_USER_IN_USERS](
state: IUsersState, state: IUsersState,
updatedUser: IUserProfile updatedUser: IUserProfile

View File

@ -1,7 +1,9 @@
import { IUsersState } from '@/store/modules/users/types' import { IUsersState } from '@/store/modules/users/types'
import { IPagination } from '@/types/api' import { IPagination } from '@/types/api'
import { IUserProfile } from '@/types/user'
export const usersState: IUsersState = { export const usersState: IUsersState = {
user: <IUserProfile>{},
users: [], users: [],
loading: false, loading: false,
pagination: <IPagination>{}, pagination: <IPagination>{},

View File

@ -11,12 +11,23 @@ import { IPagination, TPaginationPayload } from '@/types/api'
import { IAdminUserPayload, IUserProfile } from '@/types/user' import { IAdminUserPayload, IUserProfile } from '@/types/user'
export interface IUsersState { export interface IUsersState {
user: IUserProfile
users: IUserProfile[] users: IUserProfile[]
loading: boolean loading: boolean
pagination: IPagination pagination: IPagination
} }
export interface IUsersActions { export interface IUsersActions {
[USERS_STORE.ACTIONS.EMPTY_USER](
context: ActionContext<IUsersState, IRootState>
): void
[USERS_STORE.ACTIONS.EMPTY_USERS](
context: ActionContext<IUsersState, IRootState>
): void
[USERS_STORE.ACTIONS.GET_USER](
context: ActionContext<IUsersState, IRootState>,
username: string
): void
[USERS_STORE.ACTIONS.GET_USERS]( [USERS_STORE.ACTIONS.GET_USERS](
context: ActionContext<IUsersState, IRootState>, context: ActionContext<IUsersState, IRootState>,
payload: TPaginationPayload payload: TPaginationPayload
@ -28,12 +39,14 @@ export interface IUsersActions {
} }
export interface IUsersGetters { export interface IUsersGetters {
[USERS_STORE.GETTERS.USER](state: IUsersState): IUserProfile
[USERS_STORE.GETTERS.USERS](state: IUsersState): IUserProfile[] [USERS_STORE.GETTERS.USERS](state: IUsersState): IUserProfile[]
[USERS_STORE.GETTERS.USERS_LOADING](state: IUsersState): boolean [USERS_STORE.GETTERS.USERS_LOADING](state: IUsersState): boolean
[USERS_STORE.GETTERS.USERS_PAGINATION](state: IUsersState): IPagination [USERS_STORE.GETTERS.USERS_PAGINATION](state: IUsersState): IPagination
} }
export type TUsersMutations<S = IUsersState> = { export type TUsersMutations<S = IUsersState> = {
[USERS_STORE.MUTATIONS.UPDATE_USER](state: S, user: IUserProfile): void
[USERS_STORE.MUTATIONS.UPDATE_USER_IN_USERS]( [USERS_STORE.MUTATIONS.UPDATE_USER_IN_USERS](
state: S, state: S,
updatedUser: IUserProfile updatedUser: IUserProfile

View File

@ -0,0 +1,69 @@
<template>
<div id="user" v-if="user.username">
<UserHeader :user="user" />
<div class="box">
<UserInfos :user="user" :from-admin="true" />
</div>
</div>
</template>
<script lang="ts">
import {
ComputedRef,
computed,
defineComponent,
onBeforeMount,
onBeforeUnmount,
} from 'vue'
import { useRoute } from 'vue-router'
import UserHeader from '@/components/User/ProfileDisplay/UserHeader.vue'
import UserInfos from '@/components/User/ProfileDisplay/UserInfos.vue'
import { USERS_STORE } from '@/store/constants'
import { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
export default defineComponent({
name: 'UserView',
components: {
UserHeader,
UserInfos,
},
setup() {
const route = useRoute()
const store = useStore()
const user: ComputedRef<IUserProfile> = computed(
() => store.getters[USERS_STORE.GETTERS.USER]
)
onBeforeMount(() => {
if (
route.params.username &&
typeof route.params.username === 'string'
) {
store.dispatch(USERS_STORE.ACTIONS.GET_USER, route.params.username)
}
})
onBeforeUnmount(() => {
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USER)
})
return { user }
},
})
</script>
<style lang="scss" scoped>
@import '~@/scss/base.scss';
#user {
margin: auto;
width: 700px;
@media screen and (max-width: $medium-limit) {
width: 100%;
margin: 0 auto 50px auto;
}
}
</style>