Merge branch 'bug/various-fixes' into 'master'

Bug/various fixes

Closes #191, #189, #188 et #187

See merge request framasoft/mobilizon!228
This commit is contained in:
Thomas Citharel 2019-10-08 22:42:45 +02:00
commit f756c1bc37
15 changed files with 269 additions and 220 deletions

View File

@ -2,7 +2,9 @@
<div id="mobilizon"> <div id="mobilizon">
<NavBar /> <NavBar />
<main> <main>
<router-view /> <transition name="fade" mode="out-in">
<router-view />
</transition>
</main> </main>
<mobilizon-footer /> <mobilizon-footer />
</div> </div>
@ -71,18 +73,10 @@ export default class App extends Vue {
/* Buefy imports */ /* Buefy imports */
@import "~buefy/src/scss/buefy"; @import "~buefy/src/scss/buefy";
.router-enter-active, .fade-enter-active, .fade-leave-active {
.router-leave-active { transition: opacity .5s;
transition-property: opacity;
transition-duration: 0.25s;
} }
.fade-enter, .fade-leave-to {
.router-enter-active {
transition-delay: 0.25s;
}
.router-enter,
.router-leave-active {
opacity: 0; opacity: 0;
} }

View File

@ -15,7 +15,7 @@
<search-field /> <search-field />
</b-navbar-item> </b-navbar-item>
<b-navbar-dropdown v-if="currentUser.isLoggedIn" right> <b-navbar-dropdown v-if="currentActor.id && currentUser.isLoggedIn" right>
<template slot="label" v-if="currentActor" class="navbar-dropdown-profile"> <template slot="label" v-if="currentActor" class="navbar-dropdown-profile">
<figure class="image is-32x32" v-if="currentActor.avatar"> <figure class="image is-32x32" v-if="currentActor.avatar">
<img class="is-rounded" alt="avatarUrl" :src="currentActor.avatar.url"> <img class="is-rounded" alt="avatarUrl" :src="currentActor.avatar.url">
@ -82,6 +82,7 @@ import { ICurrentUser, ICurrentUserRole } from '@/types/current-user.model';
import Logo from '@/components/Logo.vue'; import Logo from '@/components/Logo.vue';
import SearchField from '@/components/SearchField.vue'; import SearchField from '@/components/SearchField.vue';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { GraphQLError } from 'graphql';
@Component({ @Component({
apollo: { apollo: {
@ -97,6 +98,7 @@ import { RouteName } from '@/router';
skip() { skip() {
return this.currentUser.isLoggedIn === false; return this.currentUser.isLoggedIn === false;
}, },
error({ graphQLErrors }) { this.handleErrors(graphQLErrors); },
}, },
config: { config: {
query: CONFIG, query: CONFIG,
@ -135,6 +137,12 @@ export default class NavBar extends Vue {
} }
} }
async handleErrors(errors: GraphQLError) {
if (errors[0].message === 'You need to be logged-in to view your list of identities') {
await this.logout();
}
}
async logout() { async logout() {
await logout(this.$apollo.provider.defaultClient); await logout(this.$apollo.provider.defaultClient);
this.$buefy.notification.open({ this.$buefy.notification.open({

View File

@ -1,8 +1,8 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
export const CREATE_USER = gql` export const CREATE_USER = gql`
mutation CreateUser($email: String!, $password: String!) { mutation CreateUser($email: String!, $password: String!, $locale: String) {
createUser(email: $email, password: $password) { createUser(email: $email, password: $password, locale: $locale) {
email, email,
confirmationSentAt confirmationSentAt
} }

View File

@ -173,7 +173,7 @@
"Past events": "Passed events", "Past events": "Passed events",
"Pick an identity": "Pick an identity", "Pick an identity": "Pick an identity",
"Please be nice to each other": "Please be nice to each other", "Please be nice to each other": "Please be nice to each other",
"Please check you spam folder if you didn't receive the email.": "Please check you spam folder if you didn't receive the email.", "Please check your spam folder if you didn't receive the email.": "Please check your spam folder if you didn't receive the email.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.", "Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.",
"Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.", "Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.",
"Please read the full rules": "Please read the full rules", "Please read the full rules": "Please read the full rules",

View File

@ -159,7 +159,7 @@
"Past events": "Événements passés", "Past events": "Événements passés",
"Pick an identity": "Choisissez une identité", "Pick an identity": "Choisissez une identité",
"Please be nice to each other": "Soyez sympas entre vous", "Please be nice to each other": "Soyez sympas entre vous",
"Please check you spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.", "Please check your spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez quil sagit dune erreur.", "Please contact this instance's Mobilizon admin if you think this is a mistake.": "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez quil sagit dune erreur.",
"Please make sure the address is correct and that the page hasn't been moved.": "Assurezvous que ladresse est correcte et que la page na pas été déplacée.", "Please make sure the address is correct and that the page hasn't been moved.": "Assurezvous que ladresse est correcte et que la page na pas été déplacée.",
"Please read the full rules": "Merci de lire les règles complètes", "Please read the full rules": "Merci de lire les règles complètes",

View File

@ -224,7 +224,7 @@
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "Lorganizator de leveniment causèt daprovar manualament las participacions daqueste eveniment. Recebretz una notificacion quand serà aprovada", "The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "Lorganizator de leveniment causèt daprovar manualament las participacions daqueste eveniment. Recebretz una notificacion quand serà aprovada",
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "Leveniment ven duna autra instància. Vòstra participacion serà confirmada aprèp quajam recebut la confirmacion de lautra instància.", "The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "Leveniment ven duna autra instància. Vòstra participacion serà confirmada aprèp quajam recebut la confirmacion de lautra instància.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Volgatz contactar ladministrator daquesta instància Mobilizon se pensatz ques una error.", "Please contact this instance's Mobilizon admin if you think this is a mistake.": "Volgatz contactar ladministrator daquesta instància Mobilizon se pensatz ques una error.",
"Please check you spam folder if you didn't receive the email.": "Mercés de verificar vòstre dorsièr de messatges indesirables savètz pas recebut lo corrièl.", "Please check your spam folder if you didn't receive the email.": "Mercés de verificar vòstre dorsièr de messatges indesirables savètz pas recebut lo corrièl.",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Saquesta identitat es lunica que pòt administrar unes grops, vos cal los suprimir den primièr per dire de poder suprimir aquesta identitat.", "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Saquesta identitat es lunica que pòt administrar unes grops, vos cal los suprimir den primièr per dire de poder suprimir aquesta identitat.",
"The content came from another server. Transfer an anonymous copy of the report?": "Lo contengut ven duna autra instància. Transferir una còpia anonima del senhalament ?", "The content came from another server. Transfer an anonymous copy of the report?": "Lo contengut ven duna autra instància. Transferir una còpia anonima del senhalament ?",
"Please make sure the address is correct and that the page hasn't been moved.": "Asseguratz-vos que ladreça es corrècta e que la pagina es pas estada desplaçada.", "Please make sure the address is correct and that the page hasn't been moved.": "Asseguratz-vos que ladreça es corrècta e que la pagina es pas estada desplaçada.",

View File

@ -22,3 +22,17 @@ export function buildFileVariable<T>(file: File | null, name: string, alt?: stri
}, },
}; };
} }
export function readFileAsync(file: File): Promise<string|ArrayBuffer|null> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsBinaryString(file);
});
}

View File

@ -94,7 +94,7 @@ import PictureUpload from '@/components/PictureUpload.vue';
import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint'; import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
import { Dialog } from 'buefy/dist/components/dialog'; import { Dialog } from 'buefy/dist/components/dialog';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { buildFileFromIPicture, buildFileVariable } from '@/utils/image'; import { buildFileFromIPicture, buildFileVariable, readFileAsync } from '@/utils/image';
import { changeIdentity } from '@/utils/auth'; import { changeIdentity } from '@/utils/auth';
@Component({ @Component({
@ -198,9 +198,11 @@ export default class EditIdentity extends Vue {
async updateIdentity() { async updateIdentity() {
try { try {
const variables = await this.buildVariables();
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: UPDATE_PERSON, mutation: UPDATE_PERSON,
variables: this.buildVariables(), variables,
update: (store, { data: { updatePerson } }) => { update: (store, { data: { updatePerson } }) => {
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES }); const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
@ -225,9 +227,11 @@ export default class EditIdentity extends Vue {
async createIdentity() { async createIdentity() {
try { try {
const variables = await this.buildVariables();
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: CREATE_PERSON, mutation: CREATE_PERSON,
variables: this.buildVariables(), variables,
update: (store, { data: { createPerson } }) => { update: (store, { data: { createPerson } }) => {
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES }); const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
@ -305,10 +309,21 @@ export default class EditIdentity extends Vue {
.replace(/[^a-z0-9._]/g, ''); .replace(/[^a-z0-9._]/g, '');
} }
private buildVariables() { private async buildVariables() {
const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`); const avatarObj = buildFileVariable(this.avatarFile, 'avatar', `${this.identity.preferredUsername}'s avatar`);
const res = Object.assign({}, this.identity, avatarObj);
return Object.assign({}, this.identity, avatarObj); /**
* If the avatar didn't change, no need to try reuploading it
*/
if (this.identity.avatar) {
const oldAvatarFile = await buildFileFromIPicture(this.identity.avatar) as File;
const oldAvatarFileContent = await readFileAsync(oldAvatarFile);
const newAvatarFileContent = await readFileAsync(this.avatarFile as File);
if (oldAvatarFileContent === newAvatarFileContent) {
res.avatar = null;
}
}
return res;
} }
private async redirectIfNoIdentitySelected (identityParam?: string) { private async redirectIfNoIdentitySelected (identityParam?: string) {

View File

@ -1,204 +1,204 @@
<template> <template>
<div class="container"> <div class="container">
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="event"> <transition appear name="fade" mode="out-in">
<div class="header-picture container"> <div v-if="event">
<figure class="image is-3by1" v-if="event.picture"> <div class="header-picture container">
<img :src="event.picture.url"> <figure class="image is-3by1" v-if="event.picture">
</figure> <img :src="event.picture.url">
<figure class="image is-3by1" v-else> </figure>
<img src="https://picsum.photos/600/200/"> <figure class="image is-3by1" v-else>
</figure> <img src="https://picsum.photos/600/200/">
</div> </figure>
<section> </div>
<div class="title-and-participate-button"> <section>
<div class="title-wrapper"> <div class="title-and-participate-button">
<div class="date-component"> <div class="title-wrapper">
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon> <div class="date-component">
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
</div>
<h1 class="title">{{ event.title }}</h1>
</div>
<div class="has-text-right" v-if="new Date(endDate) > new Date()">
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
</small>
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
</small>
<participation-button
v-if="currentActor.id && !actorIsOrganizer && !event.draft"
:participation="participations[0]"
:current-actor="currentActor"
@joinEvent="joinEvent"
@joinModal="isJoinModalActive = true"
@confirmLeave="confirmLeave"
/>
</div>
<div v-else>
<button class="button is-primary" type="button" slot="trigger" disabled>
<template>
<span>{{ $t('Event already passed')}}</span>
</template>
<b-icon icon="menu-down"></b-icon>
</button>
</div> </div>
<h1 class="title">{{ event.title }}</h1>
</div> </div>
<div class="has-text-right" v-if="new Date(endDate) > new Date()"> <div class="metadata columns">
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant"> <div class="column is-three-quarters-desktop">
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }} <p class="tags" v-if="event.category || event.tags.length > 0">
</small> <b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant"> <b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }} <span v-if="event.tags > 0"></span>
</small> <span class="visibility" v-if="!event.draft">
<participation-button <b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
v-if="currentActor.id && !actorIsOrganizer && !event.draft" <b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
:participation="participations[0]" </span>
:current-actor="currentActor" </p>
@joinEvent="joinEvent" <div class="date-and-add-to-calendar">
@joinModal="isJoinModalActive = true" <div class="date-and-privacy" v-if="event.beginsOn">
@confirmLeave="confirmLeave" <b-icon icon="calendar-clock" />
/> <event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
</div>
<a class="add-to-calendar" @click="downloadIcsEvent()" v-if="!event.draft">
<b-icon icon="calendar-plus" />
{{ $t('Add to my calendar') }}
</a>
</div>
<p class="slug">
{{ event.slug }}
</p>
</div>
<div class="column sidebar">
<div class="field has-addons" v-if="currentActor.id">
<p class="control" v-if="actorIsOrganizer || event.draft">
<router-link
class="button"
:to="{ name: RouteName.EDIT_EVENT, params: {eventId: event.uuid}}"
>
{{ $t('Edit') }}
</router-link>
</p>
<p class="control" v-if="actorIsOrganizer || event.draft">
<a class="button is-danger" @click="openDeleteEventModalWrapper">
{{ $t('Delete') }}
</a>
</p>
<p class="control">
<a class="button is-danger" @click="isReportModalActive = true">
{{ $t('Report') }}
</a>
</p>
</div>
<div class="address-wrapper">
<b-icon icon="map" />
<span v-if="!event.physicalAddress">{{ $t('No address defined') }}</span>
<div class="address" v-if="event.physicalAddress">
<address>
<span class="addressDescription" :title="event.physicalAddress.description">{{ event.physicalAddress.description }}</span>
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
<span>{{ event.physicalAddress.postalCode }} {{ event.physicalAddress.locality }}</span>
</address>
<span class="map-show-button" @click="showMap = !showMap" v-if="event.physicalAddress && event.physicalAddress.geom">
{{ $t('Show map') }}
</span>
</div>
<b-modal v-if="event.physicalAddress && event.physicalAddress.geom" :active.sync="showMap" scroll="keep">
<div class="map">
<map-leaflet
:coords="event.physicalAddress.geom"
:popup="event.physicalAddress.description"
/>
</div>
</b-modal>
</div>
<div class="organizer">
<span>
<span v-if="event.organizerActor">
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
</span>
<figure v-if="event.organizerActor.avatar" class="image is-48x48">
<img
class="is-rounded"
:src="event.organizerActor.avatar.url"
:alt="event.organizerActor.avatar.alt" />
</figure>
</span>
</div>
</div>
</div> </div>
<div v-else> </section>
<button class="button is-primary" type="button" slot="trigger" disabled> <div class="description">
<template> <div class="description-container container">
<span>{{ $t('Event already passed')}}</span> <h3 class="title">
</template> {{ $t('About this event') }}
<b-icon icon="menu-down"></b-icon> </h3>
</button> <p v-if="!event.description">
{{ $t("The event organizer didn't add any description.") }}
</p>
<div class="columns" v-else>
<div class="column is-half" v-html="event.description">
</div>
</div>
</div> </div>
</div> </div>
<div class="metadata columns"> <section class="share" v-if="!event.draft">
<div class="column is-three-quarters-desktop"> <div class="container">
<p class="tags" v-if="event.category || event.tags.length > 0"> <div class="columns">
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag> <div class="column is-half has-text-centered">
<!-- <span class="tag" v-if="event.category">{{ event.category }}</span>--> <h3 class="title">{{ $t('Share this event') }}</h3>
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag> <div>
<span v-if="event.tags > 0"></span> <b-icon icon="mastodon" size="is-large" type="is-primary" />
<span class="visibility" v-if="!event.draft"> <a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag> <a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag> <a :href="emailShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="email" size="is-large" type="is-primary" /></a>
</span> <!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
</p> <a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
<div class="date-and-add-to-calendar">
<div class="date-and-privacy" v-if="event.beginsOn">
<b-icon icon="calendar-clock" />
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
</div> </div>
<a class="add-to-calendar" @click="downloadIcsEvent()" v-if="!event.draft"> </div>
<b-icon icon="calendar-plus" /> <hr />
<div class="column is-half has-text-right add-to-calendar">
<h3 @click="downloadIcsEvent()">
{{ $t('Add to my calendar') }} {{ $t('Add to my calendar') }}
</a> </h3>
</div>
<p class="slug">
{{ event.slug }}
</p>
</div>
<div class="column sidebar">
<div class="field has-addons" v-if="currentActor.id">
<p class="control" v-if="actorIsOrganizer || event.draft">
<router-link
class="button"
:to="{ name: RouteName.EDIT_EVENT, params: {eventId: event.uuid}}"
>
{{ $t('Edit') }}
</router-link>
</p>
<p class="control" v-if="actorIsOrganizer || event.draft">
<a class="button is-danger" @click="openDeleteEventModalWrapper">
{{ $t('Delete') }}
</a>
</p>
<p class="control">
<a class="button is-danger" @click="isReportModalActive = true">
{{ $t('Report') }}
</a>
</p>
</div>
<div class="address-wrapper">
<b-icon icon="map" />
<span v-if="!event.physicalAddress">{{ $t('No address defined') }}</span>
<div class="address" v-if="event.physicalAddress">
<address>
<span class="addressDescription" :title="event.physicalAddress.description">{{ event.physicalAddress.description }}</span>
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
<span>{{ event.physicalAddress.postalCode }} {{ event.physicalAddress.locality }}</span>
<!-- <span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>-->
</address>
<span class="map-show-button" @click="showMap = !showMap" v-if="event.physicalAddress && event.physicalAddress.geom">
{{ $t('Show map') }}
</span>
</div>
<b-modal v-if="event.physicalAddress && event.physicalAddress.geom" :active.sync="showMap" scroll="keep">
<div class="map">
<map-leaflet
:coords="event.physicalAddress.geom"
:popup="event.physicalAddress.description"
/>
</div>
</b-modal>
</div>
<div class="organizer">
<span>
<span v-if="event.organizerActor">
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
</span>
<figure v-if="event.organizerActor.avatar" class="image is-48x48">
<img
class="is-rounded"
:src="event.organizerActor.avatar.url"
:alt="event.organizerActor.avatar.alt" />
</figure>
</span>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<div class="description"> <section class="more-events container" v-if="event.relatedEvents.length > 0">
<div class="description-container container"> <h3 class="title has-text-centered">{{ $t('These events may interest you') }}</h3>
<h3 class="title">
{{ $t('About this event') }}
</h3>
<p v-if="!event.description">
{{ $t("The event organizer didn't add any description.") }}
</p>
<div class="columns" v-else>
<div class="column is-half" v-html="event.description">
</div>
</div>
</div>
</div>
<section class="share" v-if="!event.draft">
<div class="container">
<div class="columns"> <div class="columns">
<div class="column is-half has-text-centered"> <div class="column is-one-third-desktop" v-for="relatedEvent in event.relatedEvents" :key="relatedEvent.uuid">
<h3 class="title">{{ $t('Share this event') }}</h3> <EventCard :event="relatedEvent" />
<div>
<b-icon icon="mastodon" size="is-large" type="is-primary" />
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="email" size="is-large" type="is-primary" /></a>
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
</div>
</div>
<hr />
<div class="column is-half has-text-right add-to-calendar">
<h3 @click="downloadIcsEvent()">
{{ $t('Add to my calendar') }}
</h3>
</div> </div>
</div> </div>
</section>
<b-modal :active.sync="isReportModalActive" has-modal-card ref="reportModal">
<report-modal :on-confirm="reportEvent" :title="$t('Report this event')" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
</b-modal>
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
<identity-picker v-model="identity">
<template v-slot:footer>
<footer class="modal-card-foot">
<button
class="button"
ref="cancelButton"
@click="isJoinModalActive = false">
{{ $t('Cancel') }}
</button>
<button
class="button is-primary"
ref="confirmButton"
@click="joinEvent(identity)">
{{ $t('Confirm my particpation') }}
</button>
</footer>
</template>
</identity-picker>
</b-modal>
</div> </div>
</section> </transition>
<section class="more-events container" v-if="event.relatedEvents.length > 0"> </div>
<h3 class="title has-text-centered">{{ $t('These events may interest you') }}</h3>
<div class="columns">
<div class="column is-one-third-desktop" v-for="relatedEvent in event.relatedEvents" :key="relatedEvent.uuid">
<EventCard :event="relatedEvent" />
</div>
</div>
</section>
<b-modal :active.sync="isReportModalActive" has-modal-card ref="reportModal">
<report-modal :on-confirm="reportEvent" :title="$t('Report this event')" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
</b-modal>
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
<identity-picker v-model="identity">
<template v-slot:footer>
<footer class="modal-card-foot">
<button
class="button"
ref="cancelButton"
@click="isJoinModalActive = false">
{{ $t('Cancel') }}
</button>
<button
class="button is-primary"
ref="confirmButton"
@click="joinEvent(identity)">
{{ $t('Confirm my particpation') }}
</button>
</footer>
</template>
</identity-picker>
</b-modal>
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -482,6 +482,13 @@ export default class Event extends EventMixin {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../variables"; @import "../../variables";
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
div.sidebar { div.sidebar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -30,7 +30,7 @@
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }} {{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
</b-message> </b-message>
</section> </section>
<section v-if="currentActor && goingToEvents.size > 0" class="container"> <section v-if="currentActor.id && goingToEvents.size > 0" class="container">
<h3 class="title"> <h3 class="title">
{{ $t("Upcoming") }} {{ $t("Upcoming") }}
</h3> </h3>
@ -81,8 +81,8 @@
<section class="events-featured"> <section class="events-featured">
<h3 class="title">{{ $t('Featured events') }}</h3> <h3 class="title">{{ $t('Featured events') }}</h3>
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="events.length > 0" class="columns is-multiline"> <div v-if="filteredFeaturedEvents.length > 0" class="columns is-multiline">
<div class="column is-one-third-desktop" v-for="event in events.slice(0, 6)" :key="event.uuid"> <div class="column is-one-third-desktop" v-for="event in filteredFeaturedEvents.slice(0, 6)" :key="event.uuid">
<EventCard <EventCard
:event="event" :event="event"
/> />
@ -152,7 +152,7 @@ import { IConfig } from '@/types/config.model';
}, },
}) })
export default class Home extends Vue { export default class Home extends Vue {
events: Event[] = []; events: IEvent[] = [];
locations = []; locations = [];
city = { name: null }; city = { name: null };
country = { name: null }; country = { name: null };
@ -224,6 +224,11 @@ export default class Home extends Vue {
return res; return res;
} }
get filteredFeaturedEvents() {
if (!this.currentUser.isLoggedIn || !this.currentActor.id) return this.events;
return this.events.filter(event => event.organizerActor && event.organizerActor.id !== this.currentActor.id);
}
geoLocalize() { geoLocalize() {
const router = this.$router; const router = this.$router;
const sessionCity = sessionStorage.getItem('City'); const sessionCity = sessionStorage.getItem('City');

View File

@ -115,6 +115,7 @@ export default class Register extends Vue {
credentials = { credentials = {
email: this.email, email: this.email,
password: this.password, password: this.password,
locale: 'en',
}; };
errors: object = {}; errors: object = {};
sendingValidation: boolean = false; sendingValidation: boolean = false;
@ -122,6 +123,7 @@ export default class Register extends Vue {
RouteName = RouteName; RouteName = RouteName;
async submit() { async submit() {
this.credentials.locale = this.$i18n.locale;
try { try {
this.sendingValidation = true; this.sendingValidation = true;
this.errors = {}; this.errors = {};

View File

@ -17,7 +17,7 @@
{{ $t('If an account with this email exists, we just sent another confirmation email to {email}', {email: credentials.email}) }} {{ $t('If an account with this email exists, we just sent another confirmation email to {email}', {email: credentials.email}) }}
</b-message> </b-message>
<b-message type="is-info"> <b-message type="is-info">
{{ $t("Please check you spam folder if you didn't receive the email.") }} {{ $t("Please check your spam folder if you didn't receive the email.") }}
</b-message> </b-message>
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@
{{ $t('We just sent an email to {email}', {email: credentials.email}) }} {{ $t('We just sent an email to {email}', {email: credentials.email}) }}
</b-message> </b-message>
<b-message type="is-info"> <b-message type="is-info">
{{ $t("Please check you spam folder if you didn't receive the email.") }} {{ $t("Please check your spam folder if you didn't receive the email.") }}
</b-message> </b-message>
</div> </div>
</div> </div>

View File

@ -39,6 +39,7 @@ describe('Registration', () => {
cy.get('form').contains('button.button.is-primary', 'Register').click(); cy.get('form').contains('button.button.is-primary', 'Register').click();
cy.url().should('include', '/register/profile'); cy.url().should('include', '/register/profile');
cy.wait(1000);
cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('tester'); cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('tester');
cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('tester account'); cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('tester account');
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account'); cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account');

View File

@ -31,11 +31,11 @@ defmodule MobilizonWeb.Resolvers.Event do
{:error, :events_max_limit_reached} {:error, :events_max_limit_reached}
end end
def find_event( defp find_private_event(
_parent, _parent,
%{uuid: uuid}, %{uuid: uuid},
%{context: %{current_user: %User{id: user_id}}} = _resolution %{context: %{current_user: %User{id: user_id}}} = _resolution
) do ) do
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
{:has_event, %Event{} = event} -> {:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))} {:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
@ -45,13 +45,16 @@ defmodule MobilizonWeb.Resolvers.Event do
end end
end end
def find_event(_parent, %{uuid: uuid}, _resolution) do defp find_private_event(_parent, %{uuid: uuid}, _resolution),
do: {:error, "Event with UUID #{uuid} not found"}
def find_event(parent, %{uuid: uuid} = args, resolution) do
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
{:has_event, %Event{} = event} -> {:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))} {:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
{:has_event, _} -> {:has_event, _} ->
{:error, "Event with UUID #{uuid} not found"} find_private_event(parent, args, resolution)
end end
end end