2019-06-17 17:15:27 +02:00
|
|
|
<template>
|
2020-06-25 11:36:35 +02:00
|
|
|
<div>
|
|
|
|
<nav class="breadcrumb" aria-label="breadcrumbs">
|
|
|
|
<ul>
|
|
|
|
<li>
|
|
|
|
<router-link :to="{ name: RouteName.IDENTITIES }">{{ $t("Profiles") }}</router-link>
|
|
|
|
</li>
|
|
|
|
<li class="is-active" v-if="isUpdate && identity">
|
|
|
|
<router-link
|
|
|
|
:to="{
|
|
|
|
name: RouteName.UPDATE_IDENTITY,
|
|
|
|
params: { identityName: identity.preferredUsername },
|
|
|
|
}"
|
|
|
|
>{{ identity.name }}</router-link
|
|
|
|
>
|
|
|
|
</li>
|
|
|
|
<li class="is-active" v-else>
|
|
|
|
<router-link :to="{ name: RouteName.CREATE_IDENTITY }">{{
|
|
|
|
$t("New profile")
|
|
|
|
}}</router-link>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</nav>
|
|
|
|
<div class="root" v-if="identity">
|
|
|
|
<h1 class="title">
|
|
|
|
<span v-if="isUpdate">{{ identity.displayName() }}</span>
|
|
|
|
<span v-else>{{ $t("I create an identity") }}</span>
|
|
|
|
</h1>
|
|
|
|
|
|
|
|
<picture-upload v-model="avatarFile" class="picture-upload" />
|
|
|
|
|
|
|
|
<b-field horizontal :label="$t('Display name')">
|
2020-02-18 08:57:00 +01:00
|
|
|
<b-input
|
|
|
|
aria-required="true"
|
|
|
|
required
|
2020-06-25 11:36:35 +02:00
|
|
|
v-model="identity.name"
|
|
|
|
@input="autoUpdateUsername($event)"
|
2020-02-18 08:57:00 +01:00
|
|
|
/>
|
2020-06-25 11:36:35 +02:00
|
|
|
</b-field>
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-06-25 11:36:35 +02:00
|
|
|
<b-field
|
|
|
|
horizontal
|
|
|
|
custom-class="username-field"
|
|
|
|
expanded
|
|
|
|
:label="$t('Username')"
|
|
|
|
:message="message"
|
|
|
|
>
|
|
|
|
<b-field expanded>
|
|
|
|
<b-input
|
|
|
|
aria-required="true"
|
|
|
|
required
|
|
|
|
v-model="identity.preferredUsername"
|
|
|
|
:disabled="isUpdate"
|
|
|
|
:use-html5-validation="!isUpdate"
|
|
|
|
pattern="[a-z0-9_]+"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<p class="control">
|
|
|
|
<span class="button is-static">@{{ getInstanceHost }}</span>
|
|
|
|
</p>
|
|
|
|
</b-field>
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<b-field horizontal :label="$t('Description')">
|
|
|
|
<b-input type="textarea" aria-required="false" v-model="identity.summary" />
|
2019-06-17 17:15:27 +02:00
|
|
|
</b-field>
|
|
|
|
|
2020-06-25 11:36:35 +02:00
|
|
|
<b-notification
|
|
|
|
type="is-danger"
|
|
|
|
has-icon
|
|
|
|
aria-close-label="Close notification"
|
|
|
|
role="alert"
|
|
|
|
:key="error"
|
|
|
|
v-for="error in errors"
|
|
|
|
>{{ error }}</b-notification
|
|
|
|
>
|
|
|
|
|
|
|
|
<b-field class="submit">
|
|
|
|
<div class="control">
|
|
|
|
<button type="button" class="button is-primary" @click="submit()">
|
|
|
|
{{ $t("Save") }}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<div class="delete-identity" v-if="isUpdate">
|
|
|
|
<span @click="openDeleteIdentityConfirmation()">{{ $t("Delete this identity") }}</span>
|
|
|
|
</div>
|
2019-06-17 17:15:27 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped type="scss">
|
2020-02-18 08:57:00 +01:00
|
|
|
h1 {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.picture-upload {
|
|
|
|
margin: 30px 0;
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.submit,
|
|
|
|
.delete-identity {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.submit {
|
|
|
|
margin: 30px 0;
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.delete-identity {
|
|
|
|
text-decoration: underline;
|
|
|
|
cursor: pointer;
|
|
|
|
margin-top: 15px;
|
|
|
|
}
|
2019-11-18 17:37:38 +01:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.username-field + .field {
|
|
|
|
margin-bottom: 0;
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
</style>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2020-02-18 08:57:00 +01:00
|
|
|
import { Component, Prop, Watch } from "vue-property-decorator";
|
|
|
|
import { mixins } from "vue-class-component";
|
2019-09-11 09:59:01 +02:00
|
|
|
import {
|
|
|
|
CREATE_PERSON,
|
|
|
|
CURRENT_ACTOR_CLIENT,
|
|
|
|
DELETE_PERSON,
|
|
|
|
FETCH_PERSON,
|
|
|
|
IDENTITIES,
|
|
|
|
UPDATE_PERSON,
|
2020-02-18 08:57:00 +01:00
|
|
|
} from "../../../graphql/actor";
|
|
|
|
import { IPerson, Person } from "../../../types/actor";
|
|
|
|
import PictureUpload from "../../../components/PictureUpload.vue";
|
|
|
|
import { MOBILIZON_INSTANCE_HOST } from "../../../api/_entrypoint";
|
|
|
|
import RouteName from "../../../router/name";
|
|
|
|
import { buildFileFromIPicture, buildFileVariable, readFileAsync } from "../../../utils/image";
|
|
|
|
import { changeIdentity } from "../../../utils/auth";
|
|
|
|
import identityEditionMixin from "../../../mixins/identityEdition";
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
|
|
|
PictureUpload,
|
|
|
|
},
|
2019-09-11 09:59:01 +02:00
|
|
|
apollo: {
|
|
|
|
currentActor: {
|
|
|
|
query: CURRENT_ACTOR_CLIENT,
|
|
|
|
},
|
2019-12-17 19:32:48 +01:00
|
|
|
identity: {
|
|
|
|
query: FETCH_PERSON,
|
|
|
|
variables() {
|
|
|
|
return {
|
|
|
|
username: this.identityName,
|
|
|
|
};
|
|
|
|
},
|
2020-02-18 08:57:00 +01:00
|
|
|
skip() {
|
|
|
|
return !this.identityName;
|
|
|
|
},
|
|
|
|
update: (data) => new Person(data.fetchPerson),
|
2019-12-17 19:32:48 +01:00
|
|
|
},
|
2019-09-11 09:59:01 +02:00
|
|
|
},
|
2019-06-17 17:15:27 +02:00
|
|
|
})
|
2019-11-18 17:37:38 +01:00
|
|
|
export default class EditIdentity extends mixins(identityEditionMixin) {
|
2019-06-17 17:15:27 +02:00
|
|
|
@Prop({ type: Boolean }) isUpdate!: boolean;
|
2020-02-18 08:57:00 +01:00
|
|
|
|
2019-12-17 19:32:48 +01:00
|
|
|
@Prop({ type: String }) identityName!: string;
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
errors: string[] = [];
|
|
|
|
|
|
|
|
avatarFile: File | null = null;
|
|
|
|
|
2019-09-11 09:59:01 +02:00
|
|
|
private currentActor: IPerson | null = null;
|
2019-06-17 17:15:27 +02:00
|
|
|
|
2020-06-25 11:36:35 +02:00
|
|
|
RouteName = RouteName;
|
|
|
|
|
2019-11-18 17:37:38 +01:00
|
|
|
get message() {
|
|
|
|
if (this.isUpdate) return null;
|
2020-02-18 08:57:00 +01:00
|
|
|
return this.$t("Only alphanumeric characters and underscores are supported.");
|
2019-11-18 17:37:38 +01:00
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
@Watch("isUpdate")
|
|
|
|
async isUpdateChanged() {
|
2019-06-17 17:15:27 +02:00
|
|
|
this.resetFields();
|
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
@Watch("identityName", { immediate: true })
|
|
|
|
async onIdentityParamChanged(val: string) {
|
2019-06-17 17:15:27 +02:00
|
|
|
// Only used when we update the identity
|
2019-12-17 19:32:48 +01:00
|
|
|
if (!this.isUpdate) return;
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
await this.redirectIfNoIdentitySelected(val);
|
|
|
|
|
2019-12-17 19:32:48 +01:00
|
|
|
if (!this.identityName) {
|
2020-02-18 08:57:00 +01:00
|
|
|
return await this.$router.push({ name: "CreateIdentity" });
|
2019-11-05 15:03:35 +01:00
|
|
|
}
|
|
|
|
|
2019-12-17 19:32:48 +01:00
|
|
|
if (this.identityName && this.identity) {
|
2019-09-02 14:35:50 +02:00
|
|
|
this.avatarFile = await buildFileFromIPicture(this.identity.avatar);
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
submit() {
|
|
|
|
if (this.isUpdate) return this.updateIdentity();
|
|
|
|
|
|
|
|
return this.createIdentity();
|
|
|
|
}
|
|
|
|
|
2019-09-11 09:59:01 +02:00
|
|
|
/**
|
|
|
|
* Delete an identity
|
|
|
|
*/
|
2019-06-17 17:15:27 +02:00
|
|
|
async deleteIdentity() {
|
|
|
|
try {
|
|
|
|
await this.$apollo.mutate({
|
|
|
|
mutation: DELETE_PERSON,
|
2019-10-04 18:28:25 +02:00
|
|
|
variables: {
|
|
|
|
id: this.identity.id,
|
|
|
|
},
|
2019-06-17 17:15:27 +02:00
|
|
|
update: (store) => {
|
2020-02-18 08:57:00 +01:00
|
|
|
const data = store.readQuery<{ identities: IPerson[] }>({
|
|
|
|
query: IDENTITIES,
|
|
|
|
});
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
if (data) {
|
2020-02-18 08:57:00 +01:00
|
|
|
data.identities = data.identities.filter((i) => i.id !== this.identity.id);
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
store.writeQuery({ query: IDENTITIES, data });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.$notifier.success(
|
2020-02-18 08:57:00 +01:00
|
|
|
this.$t("Identity {displayName} deleted", {
|
|
|
|
displayName: this.identity.displayName(),
|
|
|
|
}) as string
|
2019-06-17 17:15:27 +02:00
|
|
|
);
|
2019-09-11 09:59:01 +02:00
|
|
|
/**
|
|
|
|
* If we just deleted the current identity, we need to change it to the next one
|
|
|
|
*/
|
2020-02-18 08:57:00 +01:00
|
|
|
const data = this.$apollo.provider.defaultClient.readQuery<{
|
|
|
|
identities: IPerson[];
|
|
|
|
}>({ query: IDENTITIES });
|
2019-09-11 09:59:01 +02:00
|
|
|
if (data) {
|
|
|
|
await this.maybeUpdateCurrentActorCache(data.identities[0]);
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
await this.redirectIfNoIdentitySelected();
|
|
|
|
} catch (err) {
|
|
|
|
this.handleError(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateIdentity() {
|
|
|
|
try {
|
2019-10-08 19:32:48 +02:00
|
|
|
const variables = await this.buildVariables();
|
|
|
|
|
2019-06-17 17:15:27 +02:00
|
|
|
await this.$apollo.mutate({
|
|
|
|
mutation: UPDATE_PERSON,
|
2019-10-08 19:32:48 +02:00
|
|
|
variables,
|
2019-06-17 17:15:27 +02:00
|
|
|
update: (store, { data: { updatePerson } }) => {
|
2020-02-18 08:57:00 +01:00
|
|
|
const data = store.readQuery<{ identities: IPerson[] }>({
|
|
|
|
query: IDENTITIES,
|
|
|
|
});
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
if (data) {
|
2020-02-18 08:57:00 +01:00
|
|
|
const index = data.identities.findIndex((i) => i.id === this.identity.id);
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
this.$set(data.identities, index, updatePerson);
|
2019-09-11 09:59:01 +02:00
|
|
|
this.maybeUpdateCurrentActorCache(updatePerson);
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
store.writeQuery({ query: IDENTITIES, data });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.$notifier.success(
|
2020-02-18 08:57:00 +01:00
|
|
|
this.$t("Identity {displayName} updated", {
|
|
|
|
displayName: this.identity.displayName(),
|
|
|
|
}) as string
|
2019-06-17 17:15:27 +02:00
|
|
|
);
|
|
|
|
} catch (err) {
|
|
|
|
this.handleError(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async createIdentity() {
|
|
|
|
try {
|
2019-10-08 19:32:48 +02:00
|
|
|
const variables = await this.buildVariables();
|
|
|
|
|
2019-06-17 17:15:27 +02:00
|
|
|
await this.$apollo.mutate({
|
|
|
|
mutation: CREATE_PERSON,
|
2019-10-08 19:32:48 +02:00
|
|
|
variables,
|
2019-06-17 17:15:27 +02:00
|
|
|
update: (store, { data: { createPerson } }) => {
|
2020-02-18 08:57:00 +01:00
|
|
|
const data = store.readQuery<{ identities: IPerson[] }>({
|
|
|
|
query: IDENTITIES,
|
|
|
|
});
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
if (data) {
|
|
|
|
data.identities.push(createPerson);
|
|
|
|
|
|
|
|
store.writeQuery({ query: IDENTITIES, data });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.$notifier.success(
|
2020-02-18 08:57:00 +01:00
|
|
|
this.$t("Identity {displayName} created", {
|
|
|
|
displayName: this.identity.displayName(),
|
|
|
|
}) as string
|
2019-06-17 17:15:27 +02:00
|
|
|
);
|
2019-09-11 09:59:01 +02:00
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
await this.$router.push({
|
|
|
|
name: RouteName.UPDATE_IDENTITY,
|
|
|
|
params: { identityName: this.identity.preferredUsername },
|
|
|
|
});
|
2019-06-17 17:15:27 +02:00
|
|
|
} catch (err) {
|
|
|
|
this.handleError(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 19:32:48 +01:00
|
|
|
get getInstanceHost() {
|
2019-06-17 17:15:27 +02:00
|
|
|
return MOBILIZON_INSTANCE_HOST;
|
|
|
|
}
|
|
|
|
|
|
|
|
openDeleteIdentityConfirmation() {
|
2019-08-21 11:25:09 +02:00
|
|
|
this.$buefy.dialog.prompt({
|
2020-02-18 08:57:00 +01:00
|
|
|
type: "is-danger",
|
|
|
|
title: this.$t("Delete your identity") as string,
|
|
|
|
message: `${this.$t(
|
|
|
|
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity."
|
|
|
|
)}
|
2019-09-12 11:34:01 +02:00
|
|
|
<br /><br />
|
2020-02-18 08:57:00 +01:00
|
|
|
${this.$t(
|
|
|
|
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity."
|
|
|
|
)}
|
|
|
|
${this.$t(
|
|
|
|
"Otherwise this identity will just be removed from the group administrators."
|
|
|
|
)}
|
2019-09-12 11:34:01 +02:00
|
|
|
<br /><br />
|
2020-02-18 08:57:00 +01:00
|
|
|
${this.$t('To confirm, type your identity username "{preferredUsername}"', {
|
|
|
|
preferredUsername: this.identity.preferredUsername,
|
|
|
|
})}`,
|
|
|
|
confirmText: this.$t("Delete {preferredUsername}", {
|
|
|
|
preferredUsername: this.identity.preferredUsername,
|
|
|
|
}) as string,
|
2019-06-17 17:15:27 +02:00
|
|
|
inputAttrs: {
|
|
|
|
placeholder: this.identity.preferredUsername,
|
|
|
|
pattern: this.identity.preferredUsername,
|
|
|
|
},
|
|
|
|
|
|
|
|
onConfirm: () => this.deleteIdentity(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private handleError(err: any) {
|
|
|
|
console.error(err);
|
|
|
|
|
2019-09-11 09:59:01 +02:00
|
|
|
if (err.graphQLErrors !== undefined) {
|
2020-02-18 08:57:00 +01:00
|
|
|
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
2019-09-11 09:59:01 +02:00
|
|
|
this.$notifier.error(message);
|
|
|
|
});
|
|
|
|
}
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
2019-10-08 19:32:48 +02:00
|
|
|
private async buildVariables() {
|
2020-02-18 08:57:00 +01:00
|
|
|
const avatarObj = buildFileVariable(
|
|
|
|
this.avatarFile,
|
|
|
|
"avatar",
|
|
|
|
`${this.identity.preferredUsername}'s avatar`
|
|
|
|
);
|
|
|
|
const res = { ...this.identity, ...avatarObj };
|
2019-10-08 19:32:48 +02:00
|
|
|
/**
|
|
|
|
* If the avatar didn't change, no need to try reuploading it
|
|
|
|
*/
|
2019-10-08 20:00:26 +02:00
|
|
|
if (this.identity.avatar) {
|
2020-02-18 08:57:00 +01:00
|
|
|
const oldAvatarFile = (await buildFileFromIPicture(this.identity.avatar)) as File;
|
2019-10-08 20:00:26 +02:00
|
|
|
const oldAvatarFileContent = await readFileAsync(oldAvatarFile);
|
|
|
|
const newAvatarFileContent = await readFileAsync(this.avatarFile as File);
|
|
|
|
if (oldAvatarFileContent === newAvatarFileContent) {
|
|
|
|
res.avatar = null;
|
|
|
|
}
|
2019-10-08 19:32:48 +02:00
|
|
|
}
|
|
|
|
return res;
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
2019-12-17 19:32:48 +01:00
|
|
|
private async redirectIfNoIdentitySelected(identityParam?: string) {
|
2020-02-18 08:57:00 +01:00
|
|
|
if (identityParam) return;
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
await this.loadLoggedPersonIfNeeded();
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
if (this.currentActor) {
|
|
|
|
await this.$router.push({
|
|
|
|
params: { identityName: this.currentActor.preferredUsername },
|
|
|
|
});
|
2019-09-11 09:59:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async maybeUpdateCurrentActorCache(identity: IPerson) {
|
|
|
|
if (this.currentActor) {
|
|
|
|
if (this.currentActor.preferredUsername === this.identity.preferredUsername) {
|
|
|
|
await changeIdentity(this.$apollo.provider.defaultClient, identity);
|
|
|
|
}
|
|
|
|
this.currentActor = identity;
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
private async loadLoggedPersonIfNeeded(bypassCache = false) {
|
2019-09-11 09:59:01 +02:00
|
|
|
if (this.currentActor) return;
|
2019-06-17 17:15:27 +02:00
|
|
|
|
|
|
|
const result = await this.$apollo.query({
|
2019-09-11 09:59:01 +02:00
|
|
|
query: CURRENT_ACTOR_CLIENT,
|
2020-02-18 08:57:00 +01:00
|
|
|
fetchPolicy: bypassCache ? "network-only" : undefined,
|
2019-06-17 17:15:27 +02:00
|
|
|
});
|
|
|
|
|
2019-09-11 09:59:01 +02:00
|
|
|
this.currentActor = result.data.currentActor;
|
2019-06-17 17:15:27 +02:00
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
private resetFields() {
|
2019-06-17 17:15:27 +02:00
|
|
|
this.identity = new Person();
|
|
|
|
this.oldDisplayName = null;
|
|
|
|
this.avatarFile = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|