Add anonymous and remote participations
This commit is contained in:
@@ -8,117 +8,127 @@
|
||||
{{ $t('Update event {name}', { name: event.title }) }}
|
||||
</h1>
|
||||
|
||||
<div class="columns is-centered">
|
||||
<form class="column is-two-thirds-desktop" ref="form">
|
||||
<h2 class="subtitle">
|
||||
{{ $t('General information') }}
|
||||
</h2>
|
||||
<picture-upload v-model="pictureFile" :textFallback="$t('Headline picture')" />
|
||||
<form ref="form">
|
||||
<h2 class="subtitle">
|
||||
{{ $t('General information') }}
|
||||
</h2>
|
||||
<picture-upload v-model="pictureFile" :textFallback="$t('Headline picture')" />
|
||||
|
||||
<b-field :label="$t('Title')" :type="checkTitleLength[0]" :message="checkTitleLength[1]">
|
||||
<b-input size="is-large" aria-required="true" required v-model="event.title" />
|
||||
</b-field>
|
||||
<b-field :label="$t('Title')" :type="checkTitleLength[0]" :message="checkTitleLength[1]">
|
||||
<b-input size="is-large" aria-required="true" required v-model="event.title" />
|
||||
</b-field>
|
||||
|
||||
<tag-input v-model="event.tags" :data="tags" path="title" />
|
||||
<tag-input v-model="event.tags" :data="tags" path="title" />
|
||||
|
||||
<date-time-picker v-model="event.beginsOn" :label="$t('Starts on…')" />
|
||||
<date-time-picker :min-datetime="event.beginsOn" v-model="event.endsOn" :label="$t('Ends on…')" />
|
||||
<date-time-picker v-model="event.beginsOn" :label="$t('Starts on…')" />
|
||||
<date-time-picker :min-datetime="event.beginsOn" v-model="event.endsOn" :label="$t('Ends on…')" />
|
||||
<!-- <b-switch v-model="endsOnNull">{{ $t('No end date') }}</b-switch>-->
|
||||
<b-button type="is-text" @click="dateSettingsIsOpen = true">{{ $t('Date parameters')}}</b-button>
|
||||
<b-button type="is-text" @click="dateSettingsIsOpen = true">{{ $t('Date parameters')}}</b-button>
|
||||
|
||||
<address-auto-complete v-model="event.physicalAddress" />
|
||||
<address-auto-complete v-model="event.physicalAddress" />
|
||||
|
||||
<b-field :label="$t('Organizer')">
|
||||
<identity-picker-wrapper v-model="event.organizerActor" />
|
||||
</b-field>
|
||||
<b-field :label="$t('Organizer')">
|
||||
<identity-picker-wrapper v-model="event.organizerActor" />
|
||||
</b-field>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Description') }}</label>
|
||||
<editor v-model="event.description" />
|
||||
</div>
|
||||
|
||||
<b-field :label="$t('Website / URL')">
|
||||
<b-input icon="link" type="url" v-model="event.onlineAddress" placeholder="URL" />
|
||||
</b-field>
|
||||
|
||||
<!--<b-field :label="$t('Category')">
|
||||
<b-select placeholder="Select a category" v-model="event.category">
|
||||
<option
|
||||
v-for="category in categories"
|
||||
:value="category"
|
||||
:key="category"
|
||||
>{{ $t(category) }}</option>
|
||||
</b-select>
|
||||
</b-field>-->
|
||||
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Who can view this event and participate') }}
|
||||
</h2>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Description') }}</label>
|
||||
<editor v-model="event.description" />
|
||||
</div>
|
||||
|
||||
<b-field :label="$t('Website / URL')">
|
||||
<b-input icon="link" type="url" v-model="event.onlineAddress" placeholder="URL" />
|
||||
</b-field>
|
||||
|
||||
<!--<b-field :label="$t('Category')">
|
||||
<b-select placeholder="Select a category" v-model="event.category">
|
||||
<option
|
||||
v-for="category in categories"
|
||||
:value="category"
|
||||
:key="category"
|
||||
>{{ $t(category) }}</option>
|
||||
</b-select>
|
||||
</b-field>-->
|
||||
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Who can view this event and participate') }}
|
||||
</h2>
|
||||
<div class="field">
|
||||
<b-radio v-model="event.visibility"
|
||||
name="eventVisibility"
|
||||
:native-value="EventVisibility.PUBLIC">
|
||||
{{ $t('Visible everywhere on the web (public)') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio v-model="event.visibility"
|
||||
name="eventVisibility"
|
||||
:native-value="EventVisibility.UNLISTED">
|
||||
{{ $t('Only accessible through link and search (private)') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
<!-- <div class="field">
|
||||
<b-radio v-model="event.visibility"
|
||||
name="eventVisibility"
|
||||
:native-value="EventVisibility.PRIVATE">
|
||||
{{ $t('Page limited to my group (asks for auth)') }}
|
||||
:native-value="EventVisibility.PUBLIC">
|
||||
{{ $t('Visible everywhere on the web (public)') }}
|
||||
</b-radio>
|
||||
</div> -->
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Participation approval') }}</label>
|
||||
<b-switch v-model="needsApproval">
|
||||
{{ $t('I want to approve every participation request') }}
|
||||
</b-switch>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Number of places') }}</label>
|
||||
<b-switch v-model="limitedPlaces">
|
||||
{{ $t('Limited number of places') }}
|
||||
</b-switch>
|
||||
<b-radio v-model="event.visibility"
|
||||
name="eventVisibility"
|
||||
:native-value="EventVisibility.UNLISTED">
|
||||
{{ $t('Only accessible through link and search (private)') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
<!-- <div class="field">
|
||||
<b-radio v-model="event.visibility"
|
||||
name="eventVisibility"
|
||||
:native-value="EventVisibility.PRIVATE">
|
||||
{{ $t('Page limited to my group (asks for auth)') }}
|
||||
</b-radio>
|
||||
</div> -->
|
||||
|
||||
<div class="box" v-if="limitedPlaces">
|
||||
<b-field :label="$t('Number of places')">
|
||||
<b-numberinput controls-position="compact" min="1" v-model="event.options.maximumAttendeeCapacity" />
|
||||
</b-field>
|
||||
<div class="field" v-if="config && config.anonymous.participation.allowed">
|
||||
<label class="label">{{ $t('Anonymous participations') }}</label>
|
||||
<b-switch v-model="event.options.anonymousParticipation">
|
||||
{{ $t('I want to allow people to participate without an account.') }}
|
||||
<small v-if="config.anonymous.participation.validation.email.confirmationRequired">
|
||||
<br>
|
||||
{{ $t('Anonymous participants will be asked to confirm their participation through e-mail.') }}
|
||||
</small>
|
||||
</b-switch>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Participation approval') }}</label>
|
||||
<b-switch v-model="needsApproval">
|
||||
{{ $t('I want to approve every participation request') }}
|
||||
</b-switch>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('Number of places') }}</label>
|
||||
<b-switch v-model="limitedPlaces">
|
||||
{{ $t('Limited number of places') }}
|
||||
</b-switch>
|
||||
</div>
|
||||
|
||||
<div class="box" v-if="limitedPlaces">
|
||||
<b-field :label="$t('Number of places')">
|
||||
<b-numberinput controls-position="compact" min="1" v-model="event.options.maximumAttendeeCapacity" />
|
||||
</b-field>
|
||||
<!--
|
||||
<b-field>
|
||||
<b-switch v-model="event.options.showRemainingAttendeeCapacity">
|
||||
{{ $t('Show remaining number of places') }}
|
||||
</b-switch>
|
||||
</b-field>
|
||||
<b-field>
|
||||
<b-switch v-model="event.options.showRemainingAttendeeCapacity">
|
||||
{{ $t('Show remaining number of places') }}
|
||||
</b-switch>
|
||||
</b-field>
|
||||
|
||||
<b-field>
|
||||
<b-switch v-model="event.options.showParticipationPrice">
|
||||
{{ $t('Display participation price') }}
|
||||
</b-switch>
|
||||
</b-field> -->
|
||||
</div>
|
||||
<b-field>
|
||||
<b-switch v-model="event.options.showParticipationPrice">
|
||||
{{ $t('Display participation price') }}
|
||||
</b-switch>
|
||||
</b-field> -->
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Public comment moderation') }}
|
||||
</h2>
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Public comment moderation') }}
|
||||
</h2>
|
||||
|
||||
<div class="field">
|
||||
<b-radio v-model="event.options.commentModeration"
|
||||
name="commentModeration"
|
||||
:native-value="CommentModeration.ALLOW_ALL">
|
||||
{{ $t('Allow all comments') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio v-model="event.options.commentModeration"
|
||||
name="commentModeration"
|
||||
:native-value="CommentModeration.ALLOW_ALL">
|
||||
{{ $t('Allow all comments') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
|
||||
<!-- <div class="field">-->
|
||||
<!-- <b-radio v-model="event.options.commentModeration"-->
|
||||
@@ -128,43 +138,42 @@
|
||||
<!-- </b-radio>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="field">
|
||||
<b-radio v-model="event.options.commentModeration"
|
||||
name="commentModeration"
|
||||
:native-value="CommentModeration.CLOSED">
|
||||
{{ $t('Close comments for all (except for admins)') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio v-model="event.options.commentModeration"
|
||||
name="commentModeration"
|
||||
:native-value="CommentModeration.CLOSED">
|
||||
{{ $t('Close comments for all (except for admins)') }}
|
||||
</b-radio>
|
||||
</div>
|
||||
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Status') }}
|
||||
</h2>
|
||||
<h2 class="subtitle">
|
||||
{{ $t('Status') }}
|
||||
</h2>
|
||||
|
||||
<b-field>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-warning"
|
||||
:native-value="EventStatus.TENTATIVE">
|
||||
<b-icon icon="calendar-question" />
|
||||
{{ $t('Tentative: Will be confirmed later') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-success"
|
||||
:native-value="EventStatus.CONFIRMED">
|
||||
<b-icon icon="calendar-check" />
|
||||
{{ $t('Confirmed: Will happen') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-danger"
|
||||
:native-value="EventStatus.CANCELLED">
|
||||
<b-icon icon="calendar-remove" />
|
||||
{{ $t("Cancelled: Won't happen") }}
|
||||
</b-radio-button>
|
||||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
<b-field>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-warning"
|
||||
:native-value="EventStatus.TENTATIVE">
|
||||
<b-icon icon="calendar-question" />
|
||||
{{ $t('Tentative: Will be confirmed later') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-success"
|
||||
:native-value="EventStatus.CONFIRMED">
|
||||
<b-icon icon="calendar-check" />
|
||||
{{ $t('Confirmed: Will happen') }}
|
||||
</b-radio-button>
|
||||
<b-radio-button v-model="event.status"
|
||||
name="status"
|
||||
type="is-danger"
|
||||
:native-value="EventStatus.CANCELLED">
|
||||
<b-icon icon="calendar-remove" />
|
||||
{{ $t("Cancelled: Won't happen") }}
|
||||
</b-radio-button>
|
||||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
<b-modal :active.sync="dateSettingsIsOpen" has-modal-card trap-focus>
|
||||
<form action="">
|
||||
@@ -227,6 +236,10 @@
|
||||
<style lang="scss" scoped>
|
||||
@import "@/variables.scss";
|
||||
|
||||
main section > .container {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
h2.subtitle {
|
||||
margin: 10px 0;
|
||||
|
||||
@@ -239,8 +252,7 @@
|
||||
|
||||
section {
|
||||
& > .container {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
@@ -288,18 +300,17 @@ import { buildFileFromIPicture, buildFileVariable, readFileAsync } from '@/utils
|
||||
import IdentityPickerWrapper from '@/views/Account/IdentityPickerWrapper.vue';
|
||||
import { RouteName } from '@/router';
|
||||
import 'intersection-observer';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
|
||||
const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
||||
|
||||
@Component({
|
||||
components: { IdentityPickerWrapper, AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor: EditorComponent },
|
||||
apollo: {
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
tags: {
|
||||
query: TAGS,
|
||||
},
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
tags: TAGS,
|
||||
config: CONFIG,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
@@ -319,6 +330,7 @@ export default class EditEvent extends Vue {
|
||||
currentActor = new Person();
|
||||
tags: ITag[] = [];
|
||||
event: IEvent = new EventModel();
|
||||
config!: IConfig;
|
||||
unmodifiedEvent!: IEvent;
|
||||
pictureFile: File | null = null;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
<template>
|
||||
<div class="container">
|
||||
<b-loading :active.sync="$apollo.loading" />
|
||||
@@ -7,7 +5,7 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
<div>
|
||||
<div class="header-picture" v-if="event.picture" :style="`background-image: url('${event.picture.url}')`" />
|
||||
<div class="header-picture-default" v-else />
|
||||
<section>
|
||||
<section class="section">
|
||||
<div class="title-and-participate-button">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component">
|
||||
@@ -33,18 +31,39 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
<small v-if="event.options.maximumAttendeeCapacity">
|
||||
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
|
||||
</small>
|
||||
<b-tooltip type="is-dark" v-if="!event.local" :label="$t('The actual number of participants may differ, as this event is hosted on another instance.')">
|
||||
<b-icon size="is-small" icon="help-circle-outline" />
|
||||
</b-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-participation has-text-right" v-if="new Date(endDate) > new Date()">
|
||||
<participation-button
|
||||
v-if="currentActor.id && !actorIsOrganizer && !event.draft && (eventCapacityOK || actorIsParticipant) && event.status !== EventStatus.CANCELLED"
|
||||
v-if="anonymousParticipation === null && (config.anonymous.participation.allowed || (currentActor.id && !actorIsOrganizer && !event.draft && (eventCapacityOK || actorIsParticipant) && event.status !== EventStatus.CANCELLED))"
|
||||
:participation="participations[0]"
|
||||
:event="event"
|
||||
:current-actor="currentActor"
|
||||
@joinEvent="joinEvent"
|
||||
@joinModal="isJoinModalActive = true"
|
||||
@confirmLeave="confirmLeave"
|
||||
/>
|
||||
<b-button type="is-text" v-if="anonymousParticipation !== null" @click="cancelAnonymousParticipation">{{ $t('Cancel anonymous participation')}}</b-button>
|
||||
<small v-if="anonymousParticipation">
|
||||
{{ $t('You are participating in this event anonymously')}}
|
||||
<b-tooltip :label="$t('This information is saved only on your computer. Click for details')">
|
||||
<router-link :to="{ name: RouteName.TERMS }">
|
||||
<b-icon size="is-small" icon="help-circle-outline" />
|
||||
</router-link>
|
||||
</b-tooltip>
|
||||
</small>
|
||||
<small v-else-if="anonymousParticipation === false">
|
||||
{{ $t("You are participating in this event anonymously but didn't confirm participation")}}
|
||||
<b-tooltip :label="$t('This information is saved only on your computer. Click for details')">
|
||||
<router-link :to="{ name: RouteName.TERMS }">
|
||||
<b-icon size="is-small" icon="help-circle-outline" />
|
||||
</router-link>
|
||||
</b-tooltip>
|
||||
</small>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button class="button is-primary" type="button" slot="trigger" disabled>
|
||||
@@ -68,7 +87,9 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
||||
</span>
|
||||
<span v-if="!event.local">
|
||||
<b-tag type="is-primary">{{ event.organizerActor.domain }}</b-tag>
|
||||
<a :href="event.url">
|
||||
<b-tag type="is-primary">{{ event.organizerActor.domain }}</b-tag>
|
||||
</a>
|
||||
</span>
|
||||
<router-link
|
||||
v-if="event.tags && event.tags.length > 0"
|
||||
@@ -165,7 +186,7 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="description" :class="{ exists: event.description }">
|
||||
<section class="description section" :class="{ exists: event.description }">
|
||||
<div class="description-container container">
|
||||
<h3 class="title">
|
||||
{{ $t('About this event') }}
|
||||
@@ -178,14 +199,14 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="comments" ref="commentsObserver">
|
||||
</section>
|
||||
<section class="comments section" ref="commentsObserver">
|
||||
<a href="#comments">
|
||||
<h3 class="title" id="comments">{{ $t('Comments') }}</h3>
|
||||
</a>
|
||||
<comment-tree v-if="loadComments" :event="event" />
|
||||
</section>
|
||||
<section class="share" v-if="!event.draft">
|
||||
<section class="share section" v-if="!event.draft">
|
||||
<div class="container">
|
||||
<div class="columns is-centered is-multiline">
|
||||
<div class="column is-half-widescreen has-text-centered">
|
||||
@@ -218,7 +239,7 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="more-events container" v-if="event.relatedEvents.length > 0">
|
||||
<section class="more-events section container" v-if="event.relatedEvents.length > 0">
|
||||
<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">
|
||||
@@ -283,6 +304,14 @@ import { RouteName } from '@/router';
|
||||
import { Address } from '@/types/address.model';
|
||||
import CommentTree from '@/components/Comment/CommentTree.vue';
|
||||
import 'intersection-observer';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import {
|
||||
AnonymousParticipationNotFoundError,
|
||||
getLeaveTokenForParticipation,
|
||||
isParticipatingInThisEvent,
|
||||
removeAnonymousParticipation,
|
||||
} from '@/services/AnonymousParticipationStorage';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -339,6 +368,7 @@ import 'intersection-observer';
|
||||
return !this.currentActor || !this.event || !this.event.id || !this.currentActor.id;
|
||||
},
|
||||
},
|
||||
config: CONFIG,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
@@ -360,6 +390,7 @@ export default class Event extends EventMixin {
|
||||
event: IEvent = new EventModel();
|
||||
currentActor!: IPerson;
|
||||
identity: IPerson = new Person();
|
||||
config!: IConfig;
|
||||
participations: IParticipant[] = [];
|
||||
oldParticipationRole!: String;
|
||||
showMap: boolean = false;
|
||||
@@ -370,6 +401,7 @@ export default class Event extends EventMixin {
|
||||
RouteName = RouteName;
|
||||
observer!: IntersectionObserver;
|
||||
loadComments: boolean = false;
|
||||
anonymousParticipation: boolean|null = null;
|
||||
|
||||
get eventTitle() {
|
||||
if (!this.event) return undefined;
|
||||
@@ -381,12 +413,22 @@ export default class Event extends EventMixin {
|
||||
return this.event.description;
|
||||
}
|
||||
|
||||
mounted() {
|
||||
async mounted() {
|
||||
this.identity = this.currentActor;
|
||||
if (this.$route.hash.includes('#comment-')) {
|
||||
this.loadComments = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this.anonymousParticipation = await this.anonymousParticipationConfirmed();
|
||||
} catch (e) {
|
||||
if (e instanceof AnonymousParticipationNotFoundError) {
|
||||
this.anonymousParticipation = null;
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry) {
|
||||
@@ -529,63 +571,14 @@ export default class Event extends EventMixin {
|
||||
cancelText: this.$t('Cancel') as string,
|
||||
type: 'is-danger',
|
||||
hasIcon: true,
|
||||
onConfirm: () => this.leaveEvent(),
|
||||
onConfirm: () => {
|
||||
if (this.currentActor.id) {
|
||||
this.leaveEvent(this.event, this.currentActor.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async leaveEvent() {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ leaveEvent: IParticipant }>({
|
||||
mutation: LEAVE_EVENT,
|
||||
variables: {
|
||||
eventId: this.event.id,
|
||||
actorId: this.currentActor.id,
|
||||
},
|
||||
update: (store, { data }) => {
|
||||
if (data == null) return;
|
||||
|
||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||
query: EVENT_PERSON_PARTICIPATION,
|
||||
variables: { eventId: this.event.id, actorId: this.currentActor.id },
|
||||
});
|
||||
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, actorId: this.currentActor.id },
|
||||
data: { person },
|
||||
});
|
||||
|
||||
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.notApproved = event.participantStats.notApproved - 1;
|
||||
} else {
|
||||
event.participantStats.going = event.participantStats.going - 1;
|
||||
event.participantStats.participant = event.participantStats.participant - 1;
|
||||
}
|
||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
this.participationCancelledMessage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('participations')
|
||||
watchParticipations() {
|
||||
if (this.participations.length > 0) {
|
||||
@@ -624,10 +617,6 @@ export default class Event extends EventMixin {
|
||||
this.$notifier.info(this.$t('Your participation status has been changed') as string);
|
||||
}
|
||||
|
||||
private participationCancelledMessage() {
|
||||
this.$notifier.success(this.$t('You have cancelled your participation') as string);
|
||||
}
|
||||
|
||||
async downloadIcsEvent() {
|
||||
const data = await (await fetch(`${GRAPHQL_API_ENDPOINT}/events/${this.uuid}/export/ics`)).text();
|
||||
const blob = new Blob([data], { type: 'text/calendar' });
|
||||
@@ -709,11 +698,30 @@ export default class Event extends EventMixin {
|
||||
if (!this.event.physicalAddress) return null;
|
||||
return new Address(this.event.physicalAddress);
|
||||
}
|
||||
|
||||
async anonymousParticipationConfirmed(): Promise<boolean> {
|
||||
return await isParticipatingInThisEvent(this.uuid);
|
||||
}
|
||||
|
||||
async cancelAnonymousParticipation() {
|
||||
const token = await getLeaveTokenForParticipation(this.uuid) as String;
|
||||
await this.leaveEvent(this.event, this.config.anonymous.actorId, token);
|
||||
await removeAnonymousParticipation(this.uuid);
|
||||
this.anonymousParticipation = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
|
||||
.section {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
main > .container {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
@@ -821,14 +829,14 @@ export default class Event extends EventMixin {
|
||||
|
||||
div.title-and-participate-button {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
// flex-wrap: wrap;
|
||||
/*flex-flow: row wrap;*/
|
||||
justify-content: space-between;
|
||||
/*align-self: center;*/
|
||||
align-items: stretch;
|
||||
/*align-content: space-around;*/
|
||||
padding: 7.5px 10px 0;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
@@ -906,7 +914,7 @@ export default class Event extends EventMixin {
|
||||
p.tags {
|
||||
span {
|
||||
&.tag {
|
||||
margin: 0 2px 4px;
|
||||
margin: 0 2px;
|
||||
|
||||
&.is-success {
|
||||
&::before {
|
||||
@@ -919,7 +927,7 @@ export default class Event extends EventMixin {
|
||||
|
||||
margin: auto 5px;
|
||||
}
|
||||
margin-bottom: 1rem;
|
||||
//margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3.title {
|
||||
@@ -927,7 +935,7 @@ export default class Event extends EventMixin {
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 10px 0;
|
||||
//padding: 10px 0;
|
||||
min-height: 7rem;
|
||||
|
||||
&.exists {
|
||||
@@ -942,8 +950,8 @@ export default class Event extends EventMixin {
|
||||
background-image: url('../../assets/texting.svg');
|
||||
}
|
||||
}
|
||||
border-top: solid 1px #111;
|
||||
border-bottom: solid 1px #111;
|
||||
border-top: solid 1px lighten($primary, 60%);
|
||||
border-bottom: solid 1px lighten($primary, 60%);
|
||||
|
||||
.description-content {
|
||||
/deep/ h1 {
|
||||
@@ -990,8 +998,6 @@ export default class Event extends EventMixin {
|
||||
}
|
||||
|
||||
.comments {
|
||||
margin: 1rem auto 2rem;
|
||||
|
||||
a h3#comments {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="section container">
|
||||
<h1 class="title">{{ $t('Explore') }}</h1>
|
||||
<section class="hero">
|
||||
<div class="hero-body">
|
||||
<form @submit.prevent="submit()">
|
||||
<b-field :label="$t('Event')" grouped label-position="on-border">
|
||||
<b-field :label="$t('Event')" grouped group-multiline label-position="on-border">
|
||||
<b-input icon="magnify" type="search" size="is-large" expanded v-model="searchTerm" :placeholder="$t('For instance: London, Taekwondo, Architecture…')" />
|
||||
<p class="control">
|
||||
<b-button @click="submit" type="is-info" size="is-large" v-bind:disabled="searchTerm.trim().length === 0">{{ $t('Search') }}</b-button>
|
||||
@@ -17,7 +17,7 @@
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<h3 class="title">{{ $t('Featured events') }}</h3>
|
||||
<div v-if="events.length > 0" class="columns is-multiline">
|
||||
<div class="column is-one-quarter-desktop" v-for="event in events" :key="event.uuid">
|
||||
<div class="column is-one-third-desktop" v-for="event in events" :key="event.uuid">
|
||||
<EventCard
|
||||
:event="event"
|
||||
/>
|
||||
@@ -66,6 +66,16 @@ export default class Explore extends Vue {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/variables.scss";
|
||||
|
||||
main > .container {
|
||||
background: $white;
|
||||
|
||||
.hero-body {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h1.title {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<main class="container">
|
||||
<section class="section container">
|
||||
<h1 class="title">
|
||||
{{ $t('My events') }}
|
||||
</h1>
|
||||
@@ -10,7 +10,7 @@
|
||||
</h2>
|
||||
<transition-group name="list" tag="p">
|
||||
<div v-for="month in monthlyFutureParticipations" :key="month[0]">
|
||||
<h3>{{ month[0] }}</h3>
|
||||
<h3 class="upcoming-month">{{ month[0] }}</h3>
|
||||
<EventListCard
|
||||
v-for="participation in month[1]"
|
||||
:key="participation.id"
|
||||
@@ -64,7 +64,7 @@
|
||||
<b-message v-if="futureParticipations.length === 0 && pastParticipations.length === 0 && $apollo.loading === false" type="is-danger">
|
||||
{{ $t('No events found') }}
|
||||
</b-message>
|
||||
</main>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -212,13 +212,15 @@ export default class MyEvents extends Vue {
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
|
||||
main > .container {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.participation {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 3rem auto;
|
||||
|
||||
& > h2 {
|
||||
display: block;
|
||||
color: $primary;
|
||||
@@ -231,5 +233,9 @@ export default class MyEvents extends Vue {
|
||||
margin-top: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.upcoming-month {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
<b-tabs type="is-boxed" v-if="event" v-model="activeTab">
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="account-multiple"></b-icon>
|
||||
<b-icon icon="account-multiple" />
|
||||
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.going }} </b-tag> </span>
|
||||
</template>
|
||||
<template>
|
||||
<section v-if="participantsAndCreators.length > 0">
|
||||
<h2 class="title">{{ $t('Participants') }}</h2>
|
||||
<p v-if="confirmedAnonymousParticipantsCountCount > 1">
|
||||
{{ $tc('And no anonymous participations|And one anonymous participation|And {count} anonymous participations', confirmedAnonymousParticipantsCountCount, { count: confirmedAnonymousParticipantsCountCount}) }}
|
||||
</p>
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-one-quarter-desktop" v-for="participant in participantsAndCreators" :key="participant.actor.id">
|
||||
<participant-card
|
||||
v-if="participant.actor.id !== config.anonymous.actorId"
|
||||
:participant="participant"
|
||||
:accept="acceptParticipant"
|
||||
:reject="refuseParticipant"
|
||||
@@ -24,7 +28,7 @@
|
||||
</b-tab-item>
|
||||
<b-tab-item :disabled="participantStats.notApproved === 0">
|
||||
<template slot="header">
|
||||
<b-icon icon="account-multiple-plus"></b-icon>
|
||||
<b-icon icon="account-multiple-plus" />
|
||||
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.notApproved }} </b-tag> </span>
|
||||
</template>
|
||||
<template>
|
||||
@@ -75,7 +79,8 @@ import { PARTICIPANTS, UPDATE_PARTICIPANT } from '@/graphql/event';
|
||||
import ParticipantCard from '@/components/Account/ParticipantCard.vue';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -85,6 +90,7 @@ import { IPerson } from '@/types/actor';
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
config: CONFIG,
|
||||
event: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
@@ -159,6 +165,7 @@ export default class Participants extends Vue {
|
||||
queue: IParticipant[] = [];
|
||||
rejected: IParticipant[] = [];
|
||||
event!: IEvent;
|
||||
config!: IConfig;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
currentActor!: IPerson;
|
||||
@@ -179,6 +186,10 @@ export default class Participants extends Vue {
|
||||
return [];
|
||||
}
|
||||
|
||||
get confirmedAnonymousParticipantsCountCount(): number {
|
||||
return this.participantsAndCreators.filter(({ actor: { id } }) => id === this.config.anonymous.actorId).length;
|
||||
}
|
||||
|
||||
@Watch('participantStats', { deep: true })
|
||||
watchParticipantStats(stats: IEventParticipantStats) {
|
||||
if (!stats) return;
|
||||
|
||||
Reference in New Issue
Block a user