flaschengeist-users/src/components/settings/MainUserSettings.vue

295 lines
8.0 KiB
Vue

<template>
<q-dialog v-model="preview" auto-close square>
<q-card class="q-dialog-plugin">
<q-img :src="image" fit="contain">
<template #error>
<div style="width: 100%; height: 100%" class="row justify-center">
<img style="height: 100%; background: white" src="no-image.svg" />
</div>
</template>
</q-img>
</q-card>
</q-dialog>
<q-form @submit="save" @reset="reset">
<q-card-section class="fit row justify-start content-center items-center">
<q-input
v-model="userModel.firstname"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Vorname"
:rules="[notEmpty]"
autocomplete="given-name"
filled
/>
<q-input
v-model="userModel.lastname"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Nachname"
:rules="[notEmpty]"
autocomplete="family-name"
filled
/>
<q-input
v-model="userModel.display_name"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Angezeigter Name"
:rules="[notEmpty]"
autocomplete="nickname"
filled
/>
<q-input
v-model="userModel.mail"
class="col-xs-12 col-sm-6 q-pa-sm"
label="E-Mail"
:rules="[isEmail, notEmpty]"
autocomplete="email"
filled
/>
<q-input
v-model="userModel.userid"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Benutzername"
:readonly="!newUser"
:rules="newUser ? [isFreeUID, notEmpty] : []"
autocomplete="username"
filled
/>
<q-select
v-model="userModel.roles"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Rollen"
filled
multiple
use-chips
:readonly="!canSetRoles"
:options="allRoles"
option-label="name"
option-value="name"
/>
<IsoDateInput
v-model="userModel.birthday"
class="col-xs-12 col-sm-6 q-pa-sm"
label="Geburtstag"
autocomplete="bday"
/>
<q-file
v-model="avatar"
filled
clearable
label="Avatar"
accept="image/*, .jpg, .png"
class="col-xs-12 col-sm-6 q-pa-sm"
@clear="avatar = undefined"
@rejected="onAvatarRejected"
>
<template #before>
<q-avatar @click="preview = true">
<q-img
style="min-width: 100%; min-height: 100%"
:src="image"
@error="hasAvatar = false"
>
<template #error><img style="width: 100%" src="no-image.svg" /></template
></q-img>
</q-avatar>
</template>
<template #append>
<q-icon
v-if="!avatar && hasAvatar"
:name="deleteAvatar ? 'mdi-delete-restore' : 'mdi-delete'"
class="cursor-pointer"
@click="deleteAvatar = !deleteAvatar"
/>
</template>
</q-file>
</q-card-section>
<q-separator v-if="!newUser" />
<q-card-section
v-if="!newUser"
class="fit row justify-start content-center items-center q-gutter-y-md"
>
<PasswordInput
v-if="isCurrentUser"
v-model="password"
:rules="[notEmpty]"
filled
label="Passwort"
autocomplete="current-password"
class="col-xs-12 col-sm-6 q-pa-sm"
hint="Passwort muss immer eingetragen werden"
/>
<PasswordInput
v-model="newPassword"
filled
label="Neues Password"
autocomplete="new-password"
class="col-xs-12 col-sm-6 q-pa-sm"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn label="Reset" type="reset" />
<q-btn color="primary" type="submit" label="Speichern" />
</q-card-actions>
</q-form>
</template>
<script lang="ts">
import { Notify } from 'quasar';
import { IsoDateInput, PasswordInput } from '@flaschengeist/api/components';
import { defineComponent, computed, ref, onBeforeMount, PropType, watchEffect } from 'vue';
import {
avatarURL,
hasPermission,
notEmpty,
isEmail,
useMainStore,
useUserStore,
isAxiosError,
} from '@flaschengeist/api';
import { PERMISSIONS } from '../../permissions';
export default defineComponent({
name: 'MainUserSettings',
components: { IsoDateInput, PasswordInput },
props: {
user: {
required: true,
type: Object as PropType<FG.User>,
},
newUser: { type: Boolean, default: false },
},
emits: {
'update:user': (payload: FG.User) => !!payload,
},
setup(props, { emit }) {
const userStore = useUserStore();
const mainStore = useMainStore();
onBeforeMount(() => {
void userStore.getRoles(false);
});
const preview = ref(false);
const password = ref('');
const newPassword = ref('');
const avatar = ref<File>();
const userModel = ref(Object.assign({}, props.user));
const previewURL = ref<string>(); //< Preview of an avatar as data URL
const allRoles = computed(() => userStore.roles.map((role) => role.name));
const canSetRoles = computed(() => hasPermission(PERMISSIONS.SET_ROLES));
const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid);
const deleteAvatar = ref(false);
const hasAvatar = ref(true);
/* Reset model if props changed */
watchEffect(() => {
if (props.user.userid && props.user.userid !== userModel.value.userid) reset();
});
watchEffect(() => {
if (avatar.value && avatar.value instanceof File) {
let reader = new FileReader();
reader.onload = (e) => {
previewURL.value = <string | undefined>(e.target?.result || undefined);
};
reader.readAsDataURL(avatar.value);
} else previewURL.value = undefined;
});
/**
* Notify user is file does not fulfil the requirements
*/
function onAvatarRejected() {
Notify.create({
group: false,
type: 'negative',
message: 'Datei zu groß oder keine gültige Bilddatei.',
timeout: 10000,
progress: true,
actions: [{ icon: 'mdi-close', color: 'white' }],
});
avatar.value = new File([], '', {});
}
/**
* Save changes, emit user model, directly uploads / deletes avatar by using the API
*/
async function save() {
let changed: FG.User = userModel.value;
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
changed = Object.assign(changed, {
password: password.value,
});
if (newPassword.value != '') {
changed = Object.assign(changed, {
new_password: newPassword.value,
});
}
emit('update:user', changed);
if (avatar.value !== undefined && avatar.value.name) {
await userStore
.uploadAvatar(changed, avatar.value)
.catch((response) => isAxiosError(response, 400) && onAvatarRejected());
hasAvatar.value = true;
} else if (deleteAvatar.value) {
await userStore.deleteAvatar(changed);
hasAvatar.value = false;
}
password.value = '';
}
function reset() {
userModel.value = Object.assign({}, props.user);
password.value = '';
newPassword.value = '';
avatar.value = undefined;
previewURL.value = undefined;
deleteAvatar.value = false;
}
function isFreeUID(val: string) {
return (
userStore.users.findIndex((user) => user.userid === val) === -1 ||
'Benutzername ist schon vergeben'
);
}
const image = computed(() => {
if (previewURL.value) {
return previewURL.value;
}
if (!deleteAvatar.value) {
return avatarURL(props.user);
}
return 'no-image.svg';
});
return {
allRoles,
avatar,
canSetRoles,
deleteAvatar,
hasAvatar,
image,
isCurrentUser,
isEmail,
isFreeUID,
newPassword,
notEmpty,
onAvatarRejected,
password,
preview,
reset,
save,
userModel,
};
},
});
</script>