Add a dropdown on participate menu, disallow listing participations
Now requires quering the person endpoint to know if an actor participates in an event, organizers can make authenticated requests to event { participants { } } to see the pending / approved participants. Also closes #174 Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
8a3e606c15
commit
757d2cabec
|
@ -71,49 +71,10 @@ export default class App extends Vue {
|
|||
@import "variables";
|
||||
|
||||
/* Bulma imports */
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
@import "~bulma/sass/base/_all.sass";
|
||||
@import "~bulma/sass/components/card.sass";
|
||||
@import "~bulma/sass/components/media.sass";
|
||||
@import "~bulma/sass/components/message.sass";
|
||||
@import "~bulma/sass/components/modal.sass";
|
||||
@import "~bulma/sass/components/navbar.sass";
|
||||
@import "~bulma/sass/components/pagination.sass";
|
||||
@import "~bulma/sass/components/dropdown.sass";
|
||||
@import "~bulma/sass/components/breadcrumb.sass";
|
||||
@import "~bulma/sass/components/list.sass";
|
||||
@import "~bulma/sass/components/tabs";
|
||||
@import "~bulma/sass/elements/box.sass";
|
||||
@import "~bulma/sass/elements/button.sass";
|
||||
@import "~bulma/sass/elements/container.sass";
|
||||
@import "~bulma/sass/form/_all";
|
||||
@import "~bulma/sass/elements/icon.sass";
|
||||
@import "~bulma/sass/elements/image.sass";
|
||||
@import "~bulma/sass/elements/other.sass";
|
||||
@import "~bulma/sass/elements/progress.sass";
|
||||
@import "~bulma/sass/elements/tag.sass";
|
||||
@import "~bulma/sass/elements/title.sass";
|
||||
@import "~bulma/sass/elements/notification";
|
||||
@import "~bulma/sass/elements/table";
|
||||
@import "~bulma/sass/grid/_all.sass";
|
||||
@import "~bulma/sass/layout/_all.sass";
|
||||
@import "~bulma/bulma";
|
||||
|
||||
/* Buefy imports */
|
||||
@import "~buefy/src/scss/utils/_all";
|
||||
@import "~buefy/src/scss/components/datepicker";
|
||||
@import "~buefy/src/scss/components/notices";
|
||||
@import "~buefy/src/scss/components/dropdown";
|
||||
@import "~buefy/src/scss/components/autocomplete";
|
||||
@import "~buefy/src/scss/components/form";
|
||||
@import "~buefy/src/scss/components/modal";
|
||||
@import "~buefy/src/scss/components/progress";
|
||||
@import "~buefy/src/scss/components/tag";
|
||||
@import "~buefy/src/scss/components/taginput";
|
||||
@import "~buefy/src/scss/components/upload";
|
||||
@import "~buefy/src/scss/components/radio";
|
||||
@import "~buefy/src/scss/components/switch";
|
||||
@import "~buefy/src/scss/components/table";
|
||||
@import "~buefy/src/scss/components/tabs";
|
||||
@import "~buefy/src/scss/buefy";
|
||||
|
||||
.router-enter-active,
|
||||
.router-leave-active {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
@Component({})
|
||||
@Component
|
||||
export default class DateTimePicker extends Vue {
|
||||
@Prop({ required: true, type: Date }) value!: Date;
|
||||
@Prop({ required: false, type: String, default: 'Datetime' }) label!: string;
|
||||
|
|
|
@ -1,64 +1,66 @@
|
|||
<template>
|
||||
<article class="box columns">
|
||||
<div class="content column">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component" v-if="!mergedOptions.hideDate">
|
||||
<date-calendar-icon :date="participation.event.beginsOn" />
|
||||
</div>
|
||||
<h2 class="title" ref="title">{{ participation.event.title }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<span v-if="participation.event.physicalAddress && participation.event.physicalAddress.locality">{{ participation.event.physicalAddress.locality }} - </span>
|
||||
<span v-if="participation.actor.id === participation.event.organizerActor.id">{{ $t("You're organizing this event") }}</span>
|
||||
<span v-else>
|
||||
<span v-if="participation.event.beginsOn < new Date()">{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span>
|
||||
|
|
||||
<span>{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<span class="column is-narrow">
|
||||
<b-icon icon="earth" v-if=" participation.event.visibility === EventVisibility.PUBLIC" />
|
||||
<b-icon icon="lock_opened" v-if=" participation.event.visibility === EventVisibility.RESTRICTED" />
|
||||
<b-icon icon="lock" v-if=" participation.event.visibility === EventVisibility.PRIVATE" />
|
||||
</span>
|
||||
<span class="column">
|
||||
<span v-if="!participation.event.options.maximumAttendeeCapacity">
|
||||
{{ $tc('{count} participants', participation.event.participantStats.approved, { count: participation.event.participantStats.approved })}}
|
||||
</span>
|
||||
<b-progress
|
||||
v-if="participation.event.options.maximumAttendeeCapacity > 0"
|
||||
type="is-primary"
|
||||
size="is-medium"
|
||||
:value="participation.event.participantStats.approved * 100 / participation.event.options.maximumAttendeeCapacity" show-value>
|
||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.approved, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||
</b-progress>
|
||||
<span
|
||||
v-if="participation.event.participantStats.unapproved > 0">
|
||||
{{ $tc('{count} requests waiting', participation.event.participantStats.unapproved, { count: participation.event.participantStats.unapproved })}}
|
||||
</span>
|
||||
</span>
|
||||
<article class="box">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component" v-if="!mergedOptions.hideDate">
|
||||
<date-calendar-icon :date="participation.event.beginsOn" />
|
||||
</div>
|
||||
<h2 class="title" ref="title">{{ participation.event.title }}</h2>
|
||||
</div>
|
||||
<div class="actions column is-narrow">
|
||||
<ul>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<router-link :to="{ name: EventRouteName.EDIT_EVENT, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="pencil" /> {{ $t('Edit') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<router-link :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="account-multiple-plus" /> {{ $t('Manage participations') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: EventRouteName.EVENT, params: { uuid: participation.event.uuid } }"><b-icon icon="view-compact" /> {{ $t('View event page') }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="columns">
|
||||
<div class="content column">
|
||||
<div>
|
||||
<span v-if="participation.event.physicalAddress && participation.event.physicalAddress.locality">{{ participation.event.physicalAddress.locality }} - </span>
|
||||
<span v-if="participation.actor.id === participation.event.organizerActor.id">{{ $t("You're organizing this event") }}</span>
|
||||
<span v-else>
|
||||
<span v-if="participation.event.beginsOn < new Date()">{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span>
|
||||
|
|
||||
<span>{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<span class="column is-narrow">
|
||||
<b-icon icon="earth" v-if=" participation.event.visibility === EventVisibility.PUBLIC" />
|
||||
<b-icon icon="lock_opened" v-if=" participation.event.visibility === EventVisibility.RESTRICTED" />
|
||||
<b-icon icon="lock" v-if=" participation.event.visibility === EventVisibility.PRIVATE" />
|
||||
</span>
|
||||
<span class="column">
|
||||
<span v-if="!participation.event.options.maximumAttendeeCapacity">
|
||||
{{ $tc('{count} participants', participation.event.participantStats.approved, { count: participation.event.participantStats.approved })}}
|
||||
</span>
|
||||
<b-progress
|
||||
v-if="participation.event.options.maximumAttendeeCapacity > 0"
|
||||
type="is-primary"
|
||||
size="is-medium"
|
||||
:value="participation.event.participantStats.approved * 100 / participation.event.options.maximumAttendeeCapacity" show-value>
|
||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.approved, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||
</b-progress>
|
||||
<span
|
||||
v-if="participation.event.participantStats.unapproved > 0">
|
||||
{{ $tc('{count} requests waiting', participation.event.participantStats.unapproved, { count: participation.event.participantStats.unapproved })}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions column is-narrow">
|
||||
<ul>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<router-link :to="{ name: EventRouteName.EDIT_EVENT, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="pencil" /> {{ $t('Edit') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<router-link :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="account-multiple-plus" /> {{ $t('Manage participations') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: EventRouteName.EVENT, params: { uuid: participation.event.uuid } }"><b-icon icon="view-compact" /> {{ $t('View event page') }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
|
111
js/src/components/Event/ParticipationButton.vue
Normal file
111
js/src/components/Event/ParticipationButton.vue
Normal file
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div class="participation-button">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" v-if="participation && participation.role === ParticipantRole.PARTICIPANT">
|
||||
<button class="button is-success" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
<!-- {{ $t('Change my identity…')}}-->
|
||||
<!-- </b-dropdown-item>-->
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave">
|
||||
{{ $t('Cancel my participation…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
|
||||
<button class="button is-success" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t('I participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<!-- <b-dropdown-item :value="false" aria-role="listitem">-->
|
||||
<!-- {{ $t('Change my identity…')}}-->
|
||||
<!-- </b-dropdown-item>-->
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave">
|
||||
{{ $t('Cancel my participation request…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<small>{{ $t('Participation requested!')}}</small><br />
|
||||
<small>{{ $t('Waiting for organization team approval.')}}</small>
|
||||
</div>
|
||||
|
||||
<b-dropdown aria-role="list" position="is-bottom-left" v-if="!participation">
|
||||
<button class="button is-primary" type="button" slot="trigger">
|
||||
<template>
|
||||
<span>{{ $t('Participate') }}</span>
|
||||
</template>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<b-dropdown-item :value="true" aria-role="listitem" @click="joinEvent(currentActor)">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-32x32" v-if="currentActor.avatar">
|
||||
<img class="is-rounded" :src="currentActor.avatar.url" alt="" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span>{{ $t('with {identity}', {identity: currentActor.preferredUsername }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :value="false" aria-role="listitem" @click="joinModal">
|
||||
{{ $t('with another identity…')}}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
@Component
|
||||
export default class ParticipationButton extends Vue {
|
||||
@Prop({ required: true }) participation!: IParticipant;
|
||||
@Prop({ required: true }) currentActor!: IPerson;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
joinEvent(actor: IPerson) {
|
||||
this.$emit('joinEvent', actor);
|
||||
}
|
||||
|
||||
joinModal() {
|
||||
this.$emit('joinModal');
|
||||
}
|
||||
|
||||
confirmLeave() {
|
||||
this.$emit('confirmLeave');
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.participation-button {
|
||||
.dropdown {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.dropdown-disabled button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{ $t('Join event {title}', {title: event.title}) }}</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body is-flex">
|
||||
<div class="media">
|
||||
<div
|
||||
class="media-left">
|
||||
<b-icon
|
||||
icon="alert"
|
||||
type="is-warning"
|
||||
size="is-large"/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>{{ $t('Do you want to participate in {title}?', {title: event.title}) }}?</p>
|
||||
|
||||
<b-field :label="$t('Identity')">
|
||||
<identity-picker v-model="identity"></identity-picker>
|
||||
</b-field>
|
||||
|
||||
<p v-if="event.joinOptions === EventJoinOptions.RESTRICTED">
|
||||
{{ $t('The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved')}}
|
||||
</p>
|
||||
|
||||
<p v-if="!event.local">
|
||||
{{ $t('The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="close">
|
||||
{{ $t('Cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
ref="confirmButton"
|
||||
@click="confirm">
|
||||
{{ $t('Confirm my particpation') }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IEvent, EventJoinOptions } from '@/types/event.model';
|
||||
import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
IdentityPicker,
|
||||
},
|
||||
mounted() {
|
||||
this.$data.isActive = true;
|
||||
},
|
||||
})
|
||||
export default class ReportModal extends Vue {
|
||||
@Prop({ type: Function, default: () => {} }) onConfirm;
|
||||
@Prop({ type: Object }) event! : IEvent;
|
||||
@Prop({ type: Object }) defaultIdentity!: IPerson;
|
||||
|
||||
isActive: boolean = false;
|
||||
identity: IPerson = this.defaultIdentity;
|
||||
|
||||
EventJoinOptions = EventJoinOptions;
|
||||
|
||||
confirm() {
|
||||
this.onConfirm(this.identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Dialog.
|
||||
*/
|
||||
close() {
|
||||
this.isActive = false;
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.modal-card .modal-card-foot {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
|
@ -10,6 +10,9 @@ const participantQuery = `
|
|||
},
|
||||
name,
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -52,7 +55,7 @@ const optionsQuery = `
|
|||
`;
|
||||
|
||||
export const FETCH_EVENT = gql`
|
||||
query($uuid:UUID!, $roles: String) {
|
||||
query($uuid:UUID!) {
|
||||
event(uuid: $uuid) {
|
||||
id,
|
||||
uuid,
|
||||
|
@ -95,9 +98,6 @@ export const FETCH_EVENT = gql`
|
|||
# preferredUsername,
|
||||
# name,
|
||||
# },
|
||||
participants (roles: $roles) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved
|
||||
|
@ -363,9 +363,10 @@ export const DELETE_EVENT = gql`
|
|||
`;
|
||||
|
||||
export const PARTICIPANTS = gql`
|
||||
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String) {
|
||||
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String, $actorId: ID!) {
|
||||
event(uuid: $uuid) {
|
||||
participants(page: $page, limit: $limit, roles: $roles) {
|
||||
id,
|
||||
participants(page: $page, limit: $limit, roles: $roles, actorId: $actorId) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
|
@ -375,3 +376,21 @@ export const PARTICIPANTS = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const EVENT_PERSON_PARTICIPATION = gql`
|
||||
query($name: String!, $eventId: ID!) {
|
||||
person(preferredUsername: $name) {
|
||||
id,
|
||||
participations(eventId: $eventId) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -17,8 +17,13 @@
|
|||
"Are you sure you want to delete this event? This action cannot be reverted.": "Are you sure you want to delete this event? This action cannot be reverted.",
|
||||
"Before you can login, you need to click on the link inside it to validate your account": "Before you can login, you need to click on the link inside it to validate your account",
|
||||
"By {name}": "By {name}",
|
||||
"Cancel my participation request…": "Cancel my participation request…",
|
||||
"Cancel my participation…": "Cancel my participation…",
|
||||
"Cancel": "Cancel",
|
||||
"Category": "Category",
|
||||
"Change my identity…": "Change my identity…",
|
||||
"Change my password": "Change my password",
|
||||
"Change password": "Change password",
|
||||
"Change": "Change",
|
||||
"Clear": "Clear",
|
||||
"Click to select": "Click to select",
|
||||
|
@ -82,6 +87,7 @@
|
|||
"Group": "Group",
|
||||
"Groups": "Groups",
|
||||
"I create an identity": "I create an identity",
|
||||
"I participate": "I participate",
|
||||
"I want to approve every participation request": "I want to approve every participation request",
|
||||
"Identities": "Identities",
|
||||
"Identity {displayName} created": "Identity {displayName} created",
|
||||
|
@ -116,6 +122,7 @@
|
|||
"My events": "My events",
|
||||
"My identities": "My identities",
|
||||
"Name": "Name",
|
||||
"New password": "New password",
|
||||
"No address defined": "No address defined",
|
||||
"No events found": "No events found",
|
||||
"No group found": "No group found",
|
||||
|
@ -123,6 +130,7 @@
|
|||
"No participants yet.": "No participants yet.",
|
||||
"No results for \"{queryText}\"": "No results for \"{queryText}\"",
|
||||
"Number of places": "Number of places",
|
||||
"Old password": "Old password",
|
||||
"One person is going": "No one is going | One person is going | {approved} persons are going",
|
||||
"Only accessible through link and search (private)": "Only accessible through link and search (private)",
|
||||
"Opened reports": "Opened reports",
|
||||
|
@ -133,8 +141,11 @@
|
|||
"Otherwise this identity will just be removed from the group administrators.": "Otherwise this identity will just be removed from the group administrators.",
|
||||
"Page limited to my group (asks for auth)": "Page limited to my group (asks for auth)",
|
||||
"Participants": "Participants",
|
||||
"Participate": "Participate",
|
||||
"Participation approval": "Participation approval",
|
||||
"Participation requested!": "Participation requested!",
|
||||
"Password (confirmation)": "Password (confirmation)",
|
||||
"Password change": "Password change",
|
||||
"Password reset": "Password reset",
|
||||
"Password": "Password",
|
||||
"Past events": "Passed events",
|
||||
|
@ -187,6 +198,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": "The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved",
|
||||
"The event title will be ellipsed.": "The event title will be ellipsed.",
|
||||
"The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.",
|
||||
"The password was successfully changed": "The password was successfully changed",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
|
||||
"The {date} at {time}": "The {date} at {time}",
|
||||
"The {date} from {startTime} to {endTime}": "The {date} from {startTime} to {endTime}",
|
||||
|
@ -208,6 +220,7 @@
|
|||
"View event page": "View event page",
|
||||
"View everything": "View everything",
|
||||
"Visible everywhere on the web (public)": "Visible everywhere on the web (public)",
|
||||
"Waiting for organization team approval.": "Waiting for organization team approval.",
|
||||
"Waiting list": "Waiting list",
|
||||
"We just sent an email to {email}": "We just sent an email to {email}",
|
||||
"Website / URL": "Website / URL",
|
||||
|
@ -220,6 +233,7 @@
|
|||
"You announced that you're going to this event.": "You announced that you're going to this event.",
|
||||
"You are already logged-in.": "You are already logged-in.",
|
||||
"You are an organizer.": "You are an organizer.",
|
||||
"You have been disconnected": "You have been disconnected",
|
||||
"You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days",
|
||||
"You have one event today.": "You have no events today | You have one event today. | You have {count} events today",
|
||||
"You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow",
|
||||
|
@ -233,6 +247,8 @@
|
|||
"e.g. 10 Rue Jangot": "e.g. 10 Rue Jangot",
|
||||
"iCal Feed": "iCal Feed",
|
||||
"meditate a bit": "meditate a bit",
|
||||
"with another identity…": "with another identity…",
|
||||
"with {identity}": "with {identity}",
|
||||
"{actor}'s avatar": "{actor}'s avatar",
|
||||
"{approved} / {total} seats": "{approved} / {total} seats",
|
||||
"{count} participants": "{count} participants",
|
||||
|
|
|
@ -17,8 +17,13 @@
|
|||
"Are you sure you want to delete this event? This action cannot be reverted.": "Êtes-vous certain⋅e de vouloir supprimer cet événement ? Cette action ne peut être annulée.",
|
||||
"Before you can login, you need to click on the link inside it to validate your account": "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte",
|
||||
"By {name}": "Par {name}",
|
||||
"Cancel my participation request…": "Cancel my participation request…",
|
||||
"Cancel my participation…": "Annuler ma participation…",
|
||||
"Cancel": "Annuler",
|
||||
"Category": "Catégorie",
|
||||
"Change my identity…": "Changer mon identité…",
|
||||
"Change my password": "Modifier mon mot de passe",
|
||||
"Change password": "Modifier mot de passe",
|
||||
"Change": "Modifier",
|
||||
"Clear": "Effacer",
|
||||
"Click to select": "Cliquez pour sélectionner",
|
||||
|
@ -82,6 +87,7 @@
|
|||
"Group": "Groupe",
|
||||
"Groups": "Groupes",
|
||||
"I create an identity": "Je crée une identité",
|
||||
"I participate": "Je participe",
|
||||
"I want to approve every participation request": "Je veux approuver chaque demande de participation",
|
||||
"Identities": "Identités",
|
||||
"Identity {displayName} created": "Identité {displayName} créée",
|
||||
|
@ -116,6 +122,7 @@
|
|||
"My events": "Mes événements",
|
||||
"My identities": "Mes identités",
|
||||
"Name": "Nom",
|
||||
"New password": "Nouveau mot de passe",
|
||||
"No address defined": "Aucune adresse définie",
|
||||
"No events found": "Aucun événement trouvé",
|
||||
"No group found": "Aucun groupe trouvé",
|
||||
|
@ -123,6 +130,7 @@
|
|||
"No participants yet.": "Pas de participants pour le moment.",
|
||||
"No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »",
|
||||
"Number of places": "Nombre de places",
|
||||
"Old password": "Ancien mot de passe",
|
||||
"One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont",
|
||||
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)",
|
||||
"Opened reports": "Signalements ouverts",
|
||||
|
@ -133,8 +141,11 @@
|
|||
"Otherwise this identity will just be removed from the group administrators.": "Sinon cette identité sera juste supprimée des administrateurs du groupe.",
|
||||
"Page limited to my group (asks for auth)": "Accès limité à mon groupe (demande authentification)",
|
||||
"Participants": "Participants",
|
||||
"Participate": "Participer",
|
||||
"Participation approval": "Validation des participations",
|
||||
"Participation requested!": "Participation demandée !",
|
||||
"Password (confirmation)": "Mot de passe (confirmation)",
|
||||
"Password change": "Changement de mot de passe",
|
||||
"Password reset": "Réinitialisation du mot de passe",
|
||||
"Password": "Mot de passe",
|
||||
"Past events": "Événements passés",
|
||||
|
@ -187,6 +198,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": "L'organisateur⋅ice de l'événement a choisi d'approuver manuellement les participations à cet événement. Vous recevrez une notification lorsque votre participation sera approuvée",
|
||||
"The event title will be ellipsed.": "Le titre de l'événement sera ellipsé.",
|
||||
"The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.",
|
||||
"The password was successfully changed": "Le mot de passe a été changé avec succès",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.",
|
||||
"The {date} at {time}": "Le {date} à {time}",
|
||||
"The {date} from {startTime} to {endTime}": "Le {date} de {startTime} à {endTime}",
|
||||
|
@ -208,6 +220,7 @@
|
|||
"View event page": "Voir la page de l'événement",
|
||||
"View everything": "Voir tout",
|
||||
"Visible everywhere on the web (public)": "Visible partout sur le web (public)",
|
||||
"Waiting for organization team approval.": "En attente d'approbation par l'organisation.",
|
||||
"Waiting list": "Liste d'attente",
|
||||
"We just sent an email to {email}": "Nous venons d'envoyer un email à {email}",
|
||||
"Website / URL": "Site web / URL",
|
||||
|
@ -220,6 +233,7 @@
|
|||
"You announced that you're going to this event.": "Vous avez annoncé vous rendre à cet événement.",
|
||||
"You are already logged-in.": "Vous êtes déjà connecté.",
|
||||
"You are an organizer.": "Vous êtes un organisateur.",
|
||||
"You have been disconnected": "Vous avez été déconnecté⋅e",
|
||||
"You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours",
|
||||
"You have one event today.": "Vous n'avez pas d'évenement aujourd'hui | Vous avez un événement aujourd'hui. | Vous avez {count} événements aujourd'hui",
|
||||
"You have one event tomorrow.": "Vous n'avez pas d'événement demain | Vous avez un événement demain. | Vous avez {count} événements demain",
|
||||
|
@ -233,6 +247,8 @@
|
|||
"e.g. 10 Rue Jangot": "par exemple : 10 Rue Jangot",
|
||||
"iCal Feed": "Flux iCal",
|
||||
"meditate a bit": "méditez un peu",
|
||||
"with another identity…": "avec une autre identité…",
|
||||
"with {identity}": "avec {identity}",
|
||||
"{actor}'s avatar": "Avatar de {actor}",
|
||||
"{approved} / {total} seats": "{approved} / {total} places",
|
||||
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ICurrentUser } from '@/types/current-user.model';
|
||||
import { IEvent } from '@/types/event.model';
|
||||
import { IEvent, IParticipant } from '@/types/event.model';
|
||||
import { Actor, IActor } from '@/types/actor/actor.model';
|
||||
|
||||
export interface IFeedToken {
|
||||
|
@ -11,11 +11,13 @@ export interface IFeedToken {
|
|||
export interface IPerson extends IActor {
|
||||
feedTokens: IFeedToken[];
|
||||
goingToEvents: IEvent[];
|
||||
participations: IParticipant[];
|
||||
}
|
||||
|
||||
export class Person extends Actor implements IPerson {
|
||||
feedTokens: IFeedToken[] = [];
|
||||
goingToEvents: IEvent[] = [];
|
||||
participations: IParticipant[] = [];
|
||||
|
||||
constructor(hash: IPerson | {} = {}) {
|
||||
super(hash);
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
<template>
|
||||
<div class="identity-picker">
|
||||
<img class="image" v-if="currentIdentity.avatar" :src="currentIdentity.avatar.url" :alt="currentIdentity.avatar.alt"/> {{ currentIdentity.name || `@${currentIdentity.preferredUsername}` }}
|
||||
<b-button type="is-text" @click="isComponentModalActive = true">
|
||||
{{ $t('Change') }}
|
||||
</b-button>
|
||||
<b-modal :active.sync="isComponentModalActive" has-modal-card>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{ $t('Pick an identity') }}</p>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="list is-hoverable">
|
||||
<a class="list-item" v-for="identity in identities" :class="{ 'is-active': identity.id === currentIdentity.id }" @click="changeCurrentIdentity(identity)">
|
||||
<div class="media">
|
||||
<img class="media-left image" v-if="identity.avatar" :src="identity.avatar.url" />
|
||||
<div class="media-content">
|
||||
<h3>@{{ identity.preferredUsername }}</h3>
|
||||
<small>{{ identity.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{ $t('Pick an identity') }}</p>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="list is-hoverable">
|
||||
<a class="list-item" v-for="identity in identities" :class="{ 'is-active': identity.id === currentIdentity.id }" @click="changeCurrentIdentity(identity)">
|
||||
<div class="media">
|
||||
<img class="media-left image" v-if="identity.avatar" :src="identity.avatar.url" />
|
||||
<div class="media-content">
|
||||
<h3>@{{ identity.preferredUsername }}</h3>
|
||||
<small>{{ identity.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</a>
|
||||
</div>
|
||||
</b-modal>
|
||||
</section>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
@ -40,22 +33,13 @@ import { IDENTITIES } from '@/graphql/actor';
|
|||
})
|
||||
export default class IdentityPicker extends Vue {
|
||||
@Prop() value!: IActor;
|
||||
isComponentModalActive: boolean = false;
|
||||
identities: IActor[] = [];
|
||||
currentIdentity: IActor = this.value;
|
||||
|
||||
changeCurrentIdentity(identity: IActor) {
|
||||
this.currentIdentity = identity;
|
||||
this.$emit('input', identity);
|
||||
this.isComponentModalActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.identity-picker img.image {
|
||||
display: inline;
|
||||
height: 1.5em;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
</style>
|
39
js/src/views/Account/IdentityPickerWrapper.vue
Normal file
39
js/src/views/Account/IdentityPickerWrapper.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="identity-picker">
|
||||
<img class="image" v-if="currentIdentity.avatar" :src="currentIdentity.avatar.url" :alt="currentIdentity.avatar.alt"/> {{ currentIdentity.name || `@${currentIdentity.preferredUsername}` }}
|
||||
<b-button type="is-text" @click="isComponentModalActive = true">
|
||||
{{ $t('Change') }}
|
||||
</b-button>
|
||||
<b-modal :active.sync="isComponentModalActive" has-modal-card>
|
||||
<identity-picker :currentIdentity="currentIdentity" @input="relay" />
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IActor } from '@/types/actor';
|
||||
import IdentityPicker from './IdentityPicker.vue';
|
||||
|
||||
@Component({
|
||||
components: { IdentityPicker },
|
||||
})
|
||||
export default class IdentityPickerWrapper extends Vue {
|
||||
@Prop() value!: IActor;
|
||||
isComponentModalActive: boolean = false;
|
||||
currentIdentity: IActor = this.value;
|
||||
|
||||
relay(identity: IActor) {
|
||||
this.currentIdentity = identity;
|
||||
this.$emit('input', identity);
|
||||
this.isComponentModalActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.identity-picker img.image {
|
||||
display: inline;
|
||||
height: 1.5em;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
</style>
|
|
@ -92,6 +92,7 @@ export default class Register extends Vue {
|
|||
domain: null,
|
||||
feedTokens: [],
|
||||
goingToEvents: [],
|
||||
participations: [],
|
||||
};
|
||||
errors: object = {};
|
||||
validationSent: boolean = false;
|
||||
|
|
|
@ -29,7 +29,7 @@ import {EventJoinOptions} from "@/types/event.model";
|
|||
<address-auto-complete v-model="event.physicalAddress" />
|
||||
|
||||
<b-field :label="$t('Organizer')">
|
||||
<identity-picker v-model="event.organizerActor"></identity-picker>
|
||||
<identity-picker-wrapper v-model="event.organizerActor"></identity-picker-wrapper>
|
||||
</b-field>
|
||||
|
||||
<div class="field">
|
||||
|
@ -188,7 +188,6 @@ import {
|
|||
EventModel,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
EventVisibilityJoinOptions,
|
||||
} from '@/types/event.model';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { Person } from '@/types/actor';
|
||||
|
@ -200,10 +199,10 @@ import { TAGS } from '@/graphql/tags';
|
|||
import { ITag } from '@/types/tag.model';
|
||||
import AddressAutoComplete from '@/components/Event/AddressAutoComplete.vue';
|
||||
import { buildFileFromIPicture, buildFileVariable } from '@/utils/image';
|
||||
import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import IdentityPickerWrapper from '@/views/Account/IdentityPickerWrapper.vue';
|
||||
|
||||
@Component({
|
||||
components: { AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor, IdentityPicker },
|
||||
components: { IdentityPickerWrapper, AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor },
|
||||
apollo: {
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import {ParticipantRole} from "@/types/event.model";
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
<template>
|
||||
<div>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
|
@ -10,7 +13,7 @@
|
|||
<img src="https://picsum.photos/600/200/">
|
||||
</figure>
|
||||
</div>
|
||||
<section class="container">
|
||||
<section>
|
||||
<div class="title-and-participate-button">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component">
|
||||
|
@ -18,21 +21,21 @@
|
|||
</div>
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
</div>
|
||||
<span v-if="event.participantStats.approved > 0 && !actorIsParticipant()">
|
||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
|
||||
</span>
|
||||
<div v-if="!actorIsOrganizer()" class="participate-button has-text-centered">
|
||||
<a v-if="!actorIsParticipant()" @click="isJoinModalActive = true" class="button is-large is-primary is-rounded">
|
||||
<b-icon icon="circle-outline"></b-icon>
|
||||
{{ $t('Join') }}
|
||||
</a>
|
||||
<a v-if="actorIsParticipant()" @click="confirmLeave()" class="button is-large is-primary is-rounded">
|
||||
<b-icon icon="check-circle"></b-icon>
|
||||
{{ $t('Leave') }}
|
||||
</a>
|
||||
<div class="has-text-right">
|
||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||
</small>
|
||||
<small v-else>
|
||||
{{ $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"
|
||||
:participation="participations[0]"
|
||||
:current-actor="currentActor"
|
||||
@joinEvent="joinEvent"
|
||||
@joinModal="isJoinModalActive = true"
|
||||
@confirmLeave="confirmLeave"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metadata columns">
|
||||
|
@ -60,8 +63,8 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="column sidebar">
|
||||
<div class="field has-addons">
|
||||
<p class="control" v-if="actorIsOrganizer()">
|
||||
<div class="field has-addons" v-if="currentActor.id">
|
||||
<p class="control" v-if="actorIsOrganizer">
|
||||
<router-link
|
||||
class="button"
|
||||
:to="{ name: 'EditEvent', params: {eventId: event.uuid}}"
|
||||
|
@ -69,7 +72,7 @@
|
|||
{{ $t('Edit') }}
|
||||
</router-link>
|
||||
</p>
|
||||
<p class="control" v-if="actorIsOrganizer()">
|
||||
<p class="control" v-if="actorIsOrganizer">
|
||||
<a class="button is-danger" @click="openDeleteEventModalWrapper">
|
||||
{{ $t('Delete') }}
|
||||
</a>
|
||||
|
@ -133,26 +136,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="container">
|
||||
<h3 class="title">{{ $t('Participants') }}</h3>
|
||||
<router-link v-if="currentActor.id === event.organizerActor.id" :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: event.uuid } }">
|
||||
{{ $t('Manage participants') }}
|
||||
</router-link>
|
||||
<span v-if="event.participants.length === 0">{{ $t('No participants yet.') }}</span>
|
||||
<div class="columns">
|
||||
<div
|
||||
class="column"
|
||||
v-for="participant in event.participants"
|
||||
:key="participant.id"
|
||||
>
|
||||
<figure class="image is-48x48">
|
||||
<img v-if="!participant.actor.avatar.url" src="https://picsum.photos/48/48/" class="is-rounded">
|
||||
<img v-else :src="participant.actor.avatar.url" class="is-rounded">
|
||||
</figure>
|
||||
<span>{{ participant.actor.preferredUsername }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="share">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
|
@ -188,19 +171,35 @@
|
|||
<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">
|
||||
<participation-modal :on-confirm="joinEvent" :event="event" :defaultIdentity="currentActor" @close="$refs.participationModal.close()" />
|
||||
<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>
|
||||
|
||||
<script lang="ts">
|
||||
import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { EVENT_PERSON_PARTICIPATION, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { EventVisibility, IEvent, IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { RouteName } from '@/router';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
|
||||
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
|
||||
import BIcon from 'buefy/src/components/icon/Icon.vue';
|
||||
|
@ -208,11 +207,11 @@ import EventCard from '@/components/Event/EventCard.vue';
|
|||
import EventFullDate from '@/components/Event/EventFullDate.vue';
|
||||
import ActorLink from '@/components/Account/ActorLink.vue';
|
||||
import ReportModal from '@/components/Report/ReportModal.vue';
|
||||
import ParticipationModal from '@/components/Event/ParticipationModal.vue';
|
||||
import { IReport } from '@/types/report.model';
|
||||
import { CREATE_REPORT } from '@/graphql/report';
|
||||
import EventMixin from '@/mixins/event';
|
||||
import { EventRouteName } from '@/router/event';
|
||||
import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import ParticipationButton from '@/components/Event/ParticipationButton.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -222,7 +221,8 @@ import { EventRouteName } from '@/router/event';
|
|||
BIcon,
|
||||
DateCalendarIcon,
|
||||
ReportModal,
|
||||
ParticipationModal,
|
||||
IdentityPicker,
|
||||
ParticipationButton,
|
||||
// tslint:disable:space-in-parens
|
||||
'map-leaflet': () => import(/* webpackChunkName: "map" */ '@/components/Map.vue'),
|
||||
// tslint:enable
|
||||
|
@ -233,13 +233,25 @@ import { EventRouteName } from '@/router/event';
|
|||
variables() {
|
||||
return {
|
||||
uuid: this.uuid,
|
||||
roles: [ParticipantRole.CREATOR, ParticipantRole.MODERATOR, ParticipantRole.MODERATOR, ParticipantRole.PARTICIPANT].join(),
|
||||
};
|
||||
},
|
||||
},
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
participations: {
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables() {
|
||||
return {
|
||||
eventId: this.event.id,
|
||||
name: this.currentActor.preferredUsername,
|
||||
};
|
||||
},
|
||||
update: (data) => {
|
||||
if (data && data.person) return data.person.participations;
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Event extends EventMixin {
|
||||
|
@ -247,13 +259,17 @@ export default class Event extends EventMixin {
|
|||
|
||||
event!: IEvent;
|
||||
currentActor!: IPerson;
|
||||
validationSent: boolean = false;
|
||||
identity: IPerson = new Person();
|
||||
participations: IParticipant[] = [];
|
||||
showMap: boolean = false;
|
||||
isReportModalActive: boolean = false;
|
||||
isJoinModalActive: boolean = false;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
EventRouteName = EventRouteName;
|
||||
|
||||
mounted() {
|
||||
this.identity = this.currentActor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the event, then redirect to home.
|
||||
|
@ -298,6 +314,24 @@ export default class Event extends EventMixin {
|
|||
},
|
||||
update: (store, { data }) => {
|
||||
if (data == null) return;
|
||||
|
||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: this.event.id, name: identity.preferredUsername },
|
||||
});
|
||||
if (participationCachedData == null) return;
|
||||
const { person } = participationCachedData;
|
||||
if (person === null) {
|
||||
console.error('Cannot update participation cache, because of null value.');
|
||||
return;
|
||||
}
|
||||
person.participations.push(data.joinEvent);
|
||||
store.writeQuery({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: this.event.id, name: identity.preferredUsername },
|
||||
data: { person },
|
||||
});
|
||||
|
||||
const cachedData = store.readQuery<{ event: IEvent }>({ query: FETCH_EVENT, variables: { uuid: this.event.uuid } });
|
||||
if (cachedData == null) return;
|
||||
const { event } = cachedData;
|
||||
|
@ -306,9 +340,13 @@ export default class Event extends EventMixin {
|
|||
return;
|
||||
}
|
||||
|
||||
event.participants = event.participants.concat([data.joinEvent]);
|
||||
if (data.joinEvent.role === ParticipantRole.NOT_APPROVED) {
|
||||
event.participantStats.unapproved = event.participantStats.unapproved + 1;
|
||||
} else {
|
||||
event.participantStats.approved = event.participantStats.approved + 1;
|
||||
}
|
||||
|
||||
store.writeQuery({ query: FETCH_EVENT, data: { event } });
|
||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -338,19 +376,38 @@ export default class Event extends EventMixin {
|
|||
},
|
||||
update: (store, { data }) => {
|
||||
if (data == null) return;
|
||||
const cachedData = store.readQuery<{ event: IEvent }>({ query: FETCH_EVENT, variables: { uuid: this.event.uuid } });
|
||||
if (cachedData == null) return;
|
||||
const { event } = cachedData;
|
||||
if (event === null) {
|
||||
console.error('Cannot update event participant cache, because of null value.');
|
||||
|
||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername },
|
||||
});
|
||||
if (participationCachedData == null) return;
|
||||
const { person } = participationCachedData;
|
||||
if (person === null) {
|
||||
console.error('Cannot update participation cache, because of null value.');
|
||||
return;
|
||||
}
|
||||
const participation = person.participations[0];
|
||||
person.participations = [];
|
||||
store.writeQuery({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername },
|
||||
data: { person },
|
||||
});
|
||||
|
||||
event.participants = event.participants
|
||||
.filter(p => p.actor.id !== data.leaveEvent.actor.id);
|
||||
event.participantStats.approved = event.participantStats.approved - 1;
|
||||
|
||||
store.writeQuery({ query: FETCH_EVENT, data: { event } });
|
||||
const eventCachedData = store.readQuery<{ event: IEvent }>({ query: FETCH_EVENT, variables: { uuid: this.event.uuid } });
|
||||
if (eventCachedData == null) return;
|
||||
const { event } = eventCachedData;
|
||||
if (event === null) {
|
||||
console.error('Cannot update event cache, because of null value.');
|
||||
return;
|
||||
}
|
||||
if (participation.role === ParticipantRole.NOT_APPROVED) {
|
||||
event.participantStats.unapproved = event.participantStats.unapproved - 1;
|
||||
} else {
|
||||
event.participantStats.approved = event.participantStats.approved - 1;
|
||||
}
|
||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -369,17 +426,14 @@ export default class Event extends EventMixin {
|
|||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
actorIsParticipant() {
|
||||
if (this.actorIsOrganizer()) return true;
|
||||
get actorIsParticipant() {
|
||||
if (this.actorIsOrganizer) return true;
|
||||
|
||||
return this.currentActor &&
|
||||
this.event.participants
|
||||
.some(participant => participant.actor.id === this.currentActor.id);
|
||||
return this.participations.length > 0 && this.participations[0].role === ParticipantRole.PARTICIPANT;
|
||||
}
|
||||
|
||||
actorIsOrganizer() {
|
||||
return this.currentActor && this.event.organizerActor &&
|
||||
this.currentActor.id === this.event.organizerActor.id;
|
||||
get actorIsOrganizer() {
|
||||
return this.participations.length > 0 && this.participations[0].role === ParticipantRole.CREATOR;
|
||||
}
|
||||
|
||||
get twitterShareUrl(): string {
|
||||
|
|
|
@ -68,6 +68,7 @@ import { IPerson } from '@/types/actor';
|
|||
page: 1,
|
||||
limit: 10,
|
||||
roles: [ParticipantRole.PARTICIPANT].join(),
|
||||
actorId: this.currentActor.id,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -79,6 +80,7 @@ import { IPerson } from '@/types/actor';
|
|||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.CREATOR].join(),
|
||||
actorId: this.currentActor.id,
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
|
@ -91,6 +93,7 @@ import { IPerson } from '@/types/actor';
|
|||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.NOT_APPROVED].join(),
|
||||
actorId: this.currentActor.id,
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
<h3 class="title">
|
||||
{{ $t("Upcoming") }}
|
||||
</h3>
|
||||
<pre>{{ Array.from(goingToEvents.entries()) }}</pre>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-for="row in goingToEvents" class="upcoming-events">
|
||||
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
||||
|
@ -53,13 +52,12 @@
|
|||
{{ $tc('You have one event in {days} days.', row[1].length, {count: row[1].length, days: calculateDiffDays(row[0])}) }}
|
||||
</h3>
|
||||
</span>
|
||||
<div class="level">
|
||||
<div>
|
||||
<EventListCard
|
||||
v-for="participation in row[1]"
|
||||
v-if="isInLessThanSevenDays(row[0])"
|
||||
:key="participation[1].event.uuid"
|
||||
:participation="participation[1]"
|
||||
class="level-item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,12 +70,11 @@
|
|||
{{ $t("Last week") }}
|
||||
</h3>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div class="level">
|
||||
<div>
|
||||
<EventListCard
|
||||
v-for="participation in lastWeekEvents"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
class="level-item"
|
||||
:options="{ hideDate: false }"
|
||||
/>
|
||||
</div>
|
||||
|
@ -295,12 +292,6 @@ export default class Home extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
.upcoming-events {
|
||||
.level {
|
||||
margin-left: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
section.container {
|
||||
margin: auto auto 3rem;
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ export default class Report extends Vue {
|
|||
|
||||
report.notes = report.notes.concat([note]);
|
||||
|
||||
store.writeQuery({ query: REPORT, data: { report } });
|
||||
store.writeQuery({ query: REPORT, variables: { id: this.report.id }, data: { report } });
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -235,7 +235,7 @@ export default class Report extends Vue {
|
|||
const updatedReport = data.updateReportStatus;
|
||||
report.status = updatedReport.status;
|
||||
|
||||
store.writeQuery({ query: REPORT, data: { report } });
|
||||
store.writeQuery({ query: REPORT, variables: { id: this.report.id }, data: { report } });
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
@ -593,12 +593,12 @@ defmodule Mobilizon.Events do
|
|||
@spec list_participants_for_event(String.t(), list(atom()), integer | nil, integer | nil) ::
|
||||
[Participant.t()]
|
||||
def list_participants_for_event(
|
||||
uuid,
|
||||
id,
|
||||
roles \\ @default_participant_roles,
|
||||
page \\ nil,
|
||||
limit \\ nil
|
||||
) do
|
||||
uuid
|
||||
id
|
||||
|> list_participants_for_event_query()
|
||||
|> filter_role(roles)
|
||||
|> Page.paginate(page, limit)
|
||||
|
@ -688,7 +688,7 @@ defmodule Mobilizon.Events do
|
|||
Returns the list of participations for an actor.
|
||||
"""
|
||||
@spec list_event_participations_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
[Event.t()]
|
||||
[Participant.t()]
|
||||
def list_event_participations_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do
|
||||
actor_id
|
||||
|> event_participations_for_actor_query()
|
||||
|
@ -1241,13 +1241,11 @@ defmodule Mobilizon.Events do
|
|||
@spec event_participations_for_actor_query(integer) :: Ecto.Query.t()
|
||||
def event_participations_for_actor_query(actor_id) do
|
||||
from(
|
||||
e in Event,
|
||||
join: p in Participant,
|
||||
join: a in Actor,
|
||||
on: p.actor_id == a.id,
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
on: p.event_id == e.id,
|
||||
where: a.id == ^actor_id and p.role != ^:not_approved,
|
||||
preload: [:picture, :tags]
|
||||
where: p.actor_id == ^actor_id and p.role != ^:not_approved,
|
||||
preload: [:event]
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -1281,12 +1279,12 @@ defmodule Mobilizon.Events do
|
|||
end
|
||||
|
||||
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
|
||||
defp list_participants_for_event_query(event_uuid) do
|
||||
defp list_participants_for_event_query(event_id) do
|
||||
from(
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
on: p.event_id == e.id,
|
||||
where: e.uuid == ^event_uuid,
|
||||
where: e.id == ^event_id,
|
||||
preload: [:actor]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -38,34 +38,42 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
List participant for event (separate request)
|
||||
"""
|
||||
def list_participants_for_event(_parent, %{uuid: uuid, page: page, limit: limit}, _resolution) do
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, [], page, limit)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
List participants for event (through an event request)
|
||||
"""
|
||||
def list_participants_for_event(
|
||||
%Event{uuid: uuid},
|
||||
%{page: page, limit: limit, roles: roles},
|
||||
_resolution
|
||||
%Event{id: event_id},
|
||||
%{page: page, limit: limit, roles: roles, actor_id: actor_id},
|
||||
%{context: %{current_user: %User{} = user}} = _resolution
|
||||
) do
|
||||
roles =
|
||||
case roles do
|
||||
"" ->
|
||||
[]
|
||||
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
|
||||
# Check that moderator has right
|
||||
{:actor_approve_permission, true} <-
|
||||
{:actor_approve_permission, Mobilizon.Events.moderator_for_event?(event_id, actor_id)} do
|
||||
roles =
|
||||
case roles do
|
||||
"" ->
|
||||
[]
|
||||
|
||||
roles ->
|
||||
roles
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.downcase/1)
|
||||
|> Enum.map(&String.to_existing_atom/1)
|
||||
end
|
||||
roles ->
|
||||
roles
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.downcase/1)
|
||||
|> Enum.map(&String.to_existing_atom/1)
|
||||
end
|
||||
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, roles, page, limit)}
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(event_id, roles, page, limit)}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Moderator Actor ID is not owned by authenticated user"}
|
||||
|
||||
{:actor_approve_permission, _} ->
|
||||
{:error, "Provided moderator actor ID doesn't have permission on this event"}
|
||||
end
|
||||
end
|
||||
|
||||
def list_participants_for_event(_, _args, _resolution) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def stats_participants_for_event(%Event{id: id}, _args, _resolution) do
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule MobilizonWeb.Resolvers.Person do
|
|||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Users.User
|
||||
|
@ -173,27 +174,33 @@ defmodule MobilizonWeb.Resolvers.Person do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of events this person is going to
|
||||
Returns the participation for a specific event
|
||||
"""
|
||||
def person_going_to_events(%Actor{id: actor_id}, _args, %{context: %{current_user: user}}) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
events <- Events.list_event_participations_for_actor(actor) do
|
||||
{:ok, events}
|
||||
def person_participations(%Actor{id: actor_id}, %{event_id: event_id}, %{
|
||||
context: %{current_user: user}
|
||||
}) do
|
||||
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
|
||||
{:no_participant, {:ok, %Participant{} = participant}} <-
|
||||
{:no_participant, Events.get_participant(event_id, actor_id)} do
|
||||
{:ok, [participant]}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:no_participant, _} ->
|
||||
{:ok, []}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of events this person is going to
|
||||
"""
|
||||
def person_going_to_events(_parent, %{}, %{context: %{current_user: user}}) do
|
||||
with %Actor{} = actor <- Users.get_actor_for_user(user),
|
||||
events <- Events.list_event_participations_for_actor(actor) do
|
||||
{:ok, events}
|
||||
def person_participations(%Actor{id: actor_id}, _args, %{context: %{current_user: user}}) do
|
||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||
participations <- Events.list_event_participations_for_actor(actor) do
|
||||
{:ok, participations}
|
||||
else
|
||||
{:is_owned, false} ->
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -225,10 +225,11 @@ defmodule MobilizonWeb.Resolvers.User do
|
|||
@doc """
|
||||
Returns the list of events for all of this user's identities are going to
|
||||
"""
|
||||
def user_participations(_parent, args, %{
|
||||
context: %{current_user: %User{id: user_id}}
|
||||
def user_participations(%User{id: user_id}, args, %{
|
||||
context: %{current_user: %User{id: logged_user_id}}
|
||||
}) do
|
||||
with participations <-
|
||||
with true <- user_id == logged_user_id,
|
||||
participations <-
|
||||
Events.list_participations_for_user(
|
||||
user_id,
|
||||
Map.get(args, :after_datetime),
|
||||
|
|
|
@ -116,7 +116,6 @@ defmodule MobilizonWeb.Schema do
|
|||
import_fields(:person_queries)
|
||||
import_fields(:group_queries)
|
||||
import_fields(:event_queries)
|
||||
import_fields(:participant_queries)
|
||||
import_fields(:tag_queries)
|
||||
import_fields(:address_queries)
|
||||
import_fields(:config_queries)
|
||||
|
|
|
@ -56,8 +56,11 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
|
|||
)
|
||||
|
||||
@desc "The list of events this person goes to"
|
||||
field :going_to_events, list_of(:event) do
|
||||
resolve(&Person.person_going_to_events/3)
|
||||
field(:participations, list_of(:participant),
|
||||
description: "The list of events this person goes to"
|
||||
) do
|
||||
arg(:event_id, :id)
|
||||
resolve(&Person.person_participations/3)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
arg(:roles, :string, default_value: "")
|
||||
arg(:actor_id, :id)
|
||||
resolve(&Event.list_participants_for_event/3)
|
||||
end
|
||||
|
||||
|
|
|
@ -44,16 +44,6 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
|||
field(:actor, :deleted_object)
|
||||
end
|
||||
|
||||
object :participant_queries do
|
||||
@desc "Get all participants for an event uuid"
|
||||
field :participants, list_of(:participant) do
|
||||
arg(:uuid, non_null(:uuid))
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Resolvers.Event.list_participants_for_event/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :participant_mutations do
|
||||
@desc "Join an event"
|
||||
field :join_event, :participant do
|
||||
|
|
|
@ -47,7 +47,7 @@ defmodule MobilizonWeb.Schema.UserType do
|
|||
field(:role, :user_role, description: "The role for the user")
|
||||
|
||||
field(:participations, list_of(:participant),
|
||||
description: "The list of events this person goes to"
|
||||
description: "The list of events this user goes to"
|
||||
) do
|
||||
arg(:after_datetime, :datetime)
|
||||
arg(:before_datetime, :datetime)
|
||||
|
|
|
@ -128,14 +128,18 @@ defmodule Mobilizon.Service.Export.Feed do
|
|||
%FeedToken{actor: actor, user: %User{} = user} <- Events.get_feed_token(token) do
|
||||
case actor do
|
||||
%Actor{} = actor ->
|
||||
events = fetch_identity_going_to_events(actor)
|
||||
events = actor |> fetch_identity_participations() |> participations_to_events()
|
||||
{:ok, build_actor_feed(actor, events, false)}
|
||||
|
||||
nil ->
|
||||
with actors <- Users.get_actors_for_user(user),
|
||||
events <-
|
||||
actors
|
||||
|> Enum.map(&Events.list_event_participations_for_actor/1)
|
||||
|> Enum.map(fn actor ->
|
||||
actor
|
||||
|> Events.list_event_participations_for_actor()
|
||||
|> participations_to_events()
|
||||
end)
|
||||
|> Enum.concat() do
|
||||
{:ok, build_user_feed(events, user, token)}
|
||||
end
|
||||
|
@ -143,12 +147,18 @@ defmodule Mobilizon.Service.Export.Feed do
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch_identity_going_to_events(%Actor{} = actor) do
|
||||
defp fetch_identity_participations(%Actor{} = actor) do
|
||||
with events <- Events.list_event_participations_for_actor(actor) do
|
||||
events
|
||||
end
|
||||
end
|
||||
|
||||
defp participations_to_events(participations) do
|
||||
participations
|
||||
|> Enum.map(& &1.event_id)
|
||||
|> Enum.map(&Events.get_event_with_preload!/1)
|
||||
end
|
||||
|
||||
# Build an atom feed from actor and it's public events
|
||||
@spec build_user_feed(list(), User.t(), String.t()) :: String.t()
|
||||
defp build_user_feed(events, %User{email: email}, token) do
|
||||
|
|
|
@ -33,7 +33,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||
dtend: event.ends_on,
|
||||
description: event.description,
|
||||
uid: event.uuid,
|
||||
categories: [event.category] ++ (event.tags |> Enum.map(& &1.slug))
|
||||
categories: event.tags |> Enum.map(& &1.slug)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -52,7 +52,8 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||
|
||||
@spec export_private_actor(Actor.t()) :: String.t()
|
||||
def export_private_actor(%Actor{} = actor) do
|
||||
with events <- Events.list_event_participations_for_actor(actor) do
|
||||
with events <-
|
||||
actor |> Events.list_event_participations_for_actor() |> participations_to_events() do
|
||||
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
||||
end
|
||||
end
|
||||
|
@ -107,7 +108,11 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||
with actors <- Users.get_actors_for_user(user),
|
||||
events <-
|
||||
actors
|
||||
|> Enum.map(&Events.list_event_participations_for_actor/1)
|
||||
|> Enum.map(fn actor ->
|
||||
actor
|
||||
|> Events.list_event_participations_for_actor()
|
||||
|> participations_to_events()
|
||||
end)
|
||||
|> Enum.concat() do
|
||||
{:ok,
|
||||
%ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
||||
|
@ -115,4 +120,10 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp participations_to_events(participations) do
|
||||
participations
|
||||
|> Enum.map(& &1.event_id)
|
||||
|> Enum.map(&Events.get_event_with_preload!/1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# source: http://localhost:4000/api
|
||||
# timestamp: Tue Sep 24 2019 18:20:05 GMT+0200 (GMT+02:00)
|
||||
# timestamp: Wed Sep 25 2019 16:41:05 GMT+0200 (GMT+02:00)
|
||||
|
||||
schema {
|
||||
query: RootQueryType
|
||||
|
@ -287,7 +287,7 @@ type Event implements ActionLogObject {
|
|||
participantStats: ParticipantStats
|
||||
|
||||
"""The event's participants"""
|
||||
participants(limit: Int = 10, page: Int = 1, roles: String = ""): [Participant]
|
||||
participants(actorId: ID, limit: Int = 10, page: Int = 1, roles: String = ""): [Participant]
|
||||
|
||||
"""Phone address for the event"""
|
||||
phoneAddress: String
|
||||
|
@ -710,9 +710,6 @@ type Person implements Actor {
|
|||
"""Number of actors following this actor"""
|
||||
followingCount: Int
|
||||
|
||||
"""The list of events this person goes to"""
|
||||
goingToEvents: [Event]
|
||||
|
||||
"""Internal ID for this person"""
|
||||
id: ID
|
||||
|
||||
|
@ -734,6 +731,9 @@ type Person implements Actor {
|
|||
"""A list of the events this actor has organized"""
|
||||
organizedEvents: [Event]
|
||||
|
||||
"""The list of events this person goes to"""
|
||||
participations(eventId: ID): [Participant]
|
||||
|
||||
"""The actor's preferred username"""
|
||||
preferredUsername: String
|
||||
|
||||
|
@ -1128,9 +1128,6 @@ type RootQueryType {
|
|||
"""Get the current user"""
|
||||
loggedUser: User
|
||||
|
||||
"""Get all participants for an event uuid"""
|
||||
participants(limit: Int = 10, page: Int = 1, uuid: UUID!): [Participant]
|
||||
|
||||
"""Get a person by it's preferred username"""
|
||||
person(preferredUsername: String!): Person
|
||||
|
||||
|
@ -1223,7 +1220,7 @@ type User {
|
|||
"""The user's ID"""
|
||||
id: ID!
|
||||
|
||||
"""The list of events this person goes to"""
|
||||
"""The list of events this user goes to"""
|
||||
participations(afterDatetime: DateTime, beforeDatetime: DateTime, limit: Int = 10, page: Int = 1): [Participant]
|
||||
|
||||
"""The user's list of profiles (identities)"""
|
||||
|
|
|
@ -99,8 +99,8 @@ defmodule Mobilizon.EventsTest do
|
|||
assert event.ends_on == DateTime.from_naive!(~N[2010-04-17 14:00:00Z], "Etc/UTC")
|
||||
assert event.title == "some title"
|
||||
|
||||
assert hd(Events.list_participants_for_event(event.uuid)).actor.id == actor.id
|
||||
assert hd(Events.list_participants_for_event(event.uuid)).role == :creator
|
||||
assert hd(Events.list_participants_for_event(event.id)).actor.id == actor.id
|
||||
assert hd(Events.list_participants_for_event(event.id)).role == :creator
|
||||
end
|
||||
|
||||
test "create_event/1 with invalid data returns error changeset" do
|
||||
|
|
|
@ -784,7 +784,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||
assert :error == Transmogrifier.handle_incoming(reject_data)
|
||||
|
||||
# Organiser is not present since we use factories directly
|
||||
assert Events.list_participants_for_event(event.uuid) |> Enum.map(& &1.id) ==
|
||||
assert Events.list_participants_for_event(event.id) |> Enum.map(& &1.id) ==
|
||||
[]
|
||||
end
|
||||
|
||||
|
@ -812,7 +812,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||
assert activity.data["actor"] == participant_url
|
||||
|
||||
# The only participant left is the organizer
|
||||
assert Events.list_participants_for_event(event.uuid) |> Enum.map(& &1.id) == [
|
||||
assert Events.list_participants_for_event(event.id) |> Enum.map(& &1.id) == [
|
||||
organizer_participation.id
|
||||
]
|
||||
end
|
||||
|
|
|
@ -106,8 +106,8 @@ defmodule MobilizonWeb.FeedControllerTest do
|
|||
assert entry.summary in [event1.title, event2.title]
|
||||
end)
|
||||
|
||||
assert entry1.categories == [event1.category, tag1.slug]
|
||||
assert entry2.categories == [event2.category, tag1.slug, tag2.slug]
|
||||
assert entry1.categories == [tag1.slug]
|
||||
assert entry2.categories == [tag1.slug, tag2.slug]
|
||||
end
|
||||
|
||||
test "it returns a 404 page for the actor's public events iCal feed with an actor not publicly visible",
|
||||
|
@ -174,7 +174,7 @@ defmodule MobilizonWeb.FeedControllerTest do
|
|||
|
||||
assert entry1.summary == event1.title
|
||||
|
||||
assert entry1.categories == [event1.category, tag1.slug, tag2.slug]
|
||||
assert entry1.categories == [tag1.slug, tag2.slug]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -311,6 +311,7 @@ defmodule MobilizonWeb.FeedControllerTest do
|
|||
|
||||
[entry1] = ExIcal.parse(conn.resp_body)
|
||||
assert entry1.summary == event1.title
|
||||
assert entry1.categories == event1.tags |> Enum.map(& &1.slug)
|
||||
end
|
||||
|
||||
test "it returns 404 for an not existing feed", %{conn: conn} do
|
||||
|
|
|
@ -129,13 +129,15 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
actor: actor
|
||||
} do
|
||||
event = insert(:event, %{organizer_actor: actor})
|
||||
participant = insert(:participant, %{actor: actor, event: event})
|
||||
participant2 = insert(:participant, %{event: event})
|
||||
insert(:participant, %{actor: actor, event: event, role: :creator})
|
||||
user2 = insert(:user)
|
||||
actor2 = insert(:actor, user: user2)
|
||||
participant2 = insert(:participant, %{event: event, actor: actor2, role: :participant})
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
leaveEvent(
|
||||
actor_id: #{participant.actor.id},
|
||||
actor_id: #{participant2.actor.id},
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
actor {
|
||||
|
@ -150,40 +152,64 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> auth_conn(user2)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["leaveEvent"]["event"]["id"] == to_string(event.id)
|
||||
|
||||
assert json_response(res, 200)["data"]["leaveEvent"]["actor"]["id"] ==
|
||||
to_string(participant.actor.id)
|
||||
to_string(participant2.actor.id)
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
participations(eventId: "#{event.id}") {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
},
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => participant2.actor.preferred_username
|
||||
"event" => %{
|
||||
"uuid" => event.uuid,
|
||||
"title" => event.title
|
||||
},
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
]
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor2.preferred_username}") {
|
||||
participations(eventId: "#{event.id}") {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
},
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user2)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == []
|
||||
end
|
||||
|
||||
test "actor_leave_event/3 should check if the participant is the only creator", %{
|
||||
|
@ -324,17 +350,23 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
assert hd(json_response(res, 200)["errors"])["message"] =~ "Participant not found"
|
||||
end
|
||||
|
||||
test "list_participants_for_event/3 returns participants for an event", context do
|
||||
test "list_participants_for_event/3 returns participants for an event", %{
|
||||
conn: conn,
|
||||
actor: actor,
|
||||
user: user
|
||||
} do
|
||||
event =
|
||||
@event
|
||||
|> Map.put(:organizer_actor_id, context.actor.id)
|
||||
|> Map.put(:organizer_actor_id, actor.id)
|
||||
|
||||
{:ok, event} = Events.create_event(event)
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(roles: "participant,moderator,administrator,creator") {
|
||||
participants(roles: "participant,moderator,administrator,creator", actor_id: "#{
|
||||
actor.id
|
||||
}") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
|
@ -345,13 +377,16 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
|
||||
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => context.actor.preferred_username
|
||||
"preferredUsername" => actor.preferred_username
|
||||
},
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
|
@ -368,7 +403,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(page: 1, limit: 1, roles: "participant,moderator,administrator,creator") {
|
||||
participants(page: 1, limit: 1, roles: "participant,moderator,administrator,creator", actorId: "#{
|
||||
actor.id
|
||||
}") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
|
@ -379,7 +416,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
sorted_participants =
|
||||
|
@ -402,7 +440,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(page: 2, limit: 1, roles: "participant,moderator,administrator,creator") {
|
||||
participants(page: 2, limit: 1, roles: "participant,moderator,administrator,creator", actorId: "#{
|
||||
actor.id
|
||||
}") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
|
@ -413,7 +453,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
sorted_participants =
|
||||
|
@ -427,7 +468,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
assert sorted_participants == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => context.actor.preferred_username
|
||||
"preferredUsername" => actor.preferred_username
|
||||
},
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
|
|
|
@ -473,7 +473,9 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "Person with name riri not found"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_current_person/3" do
|
||||
test "get_current_person/3 can return the events the person is going to", context do
|
||||
user = insert(:user)
|
||||
actor = insert(:actor, user: user)
|
||||
|
@ -481,9 +483,11 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
query = """
|
||||
{
|
||||
loggedPerson {
|
||||
goingToEvents {
|
||||
uuid,
|
||||
title
|
||||
participations {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +498,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["loggedPerson"]["goingToEvents"] == []
|
||||
assert json_response(res, 200)["data"]["loggedPerson"]["participations"] == []
|
||||
|
||||
event = insert(:event, %{organizer_actor: actor})
|
||||
insert(:participant, %{actor: actor, event: event})
|
||||
|
@ -504,8 +508,8 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["loggedPerson"]["goingToEvents"] == [
|
||||
%{"title" => event.title, "uuid" => event.uuid}
|
||||
assert json_response(res, 200)["data"]["loggedPerson"]["participations"] == [
|
||||
%{"event" => %{"title" => event.title, "uuid" => event.uuid}}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -519,9 +523,11 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
goingToEvents {
|
||||
uuid,
|
||||
title
|
||||
participations {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -532,14 +538,16 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["goingToEvents"] == []
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == []
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor_from_other_user.preferred_username}") {
|
||||
goingToEvents {
|
||||
uuid,
|
||||
title
|
||||
participations {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -550,10 +558,45 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|
|||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["goingToEvents"] == nil
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == nil
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
"Actor id is not owned by authenticated user"
|
||||
end
|
||||
|
||||
test "find_person/3 can return the participation for an identity on a specific event",
|
||||
context do
|
||||
user = insert(:user)
|
||||
actor = insert(:actor, user: user)
|
||||
event = insert(:event, organizer_actor: actor)
|
||||
insert(:participant, event: event, actor: actor)
|
||||
|
||||
query = """
|
||||
{
|
||||
person(preferredUsername: "#{actor.preferred_username}") {
|
||||
participations(eventId: "#{event.id}") {
|
||||
event {
|
||||
uuid,
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["participations"] == [
|
||||
%{
|
||||
"event" => %{
|
||||
"uuid" => event.uuid,
|
||||
"title" => event.title
|
||||
}
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue