Merge branch 'feature/add-cancel-to-event' into 'master'

Add a cancel button to event edit & make buttons fixed

See merge request framasoft/mobilizon!213
This commit is contained in:
Thomas Citharel 2019-10-01 20:24:50 +02:00
commit b96f3bc3ad
17 changed files with 258 additions and 167 deletions

View file

@ -1,7 +1,7 @@
<template> <template>
<div id="mobilizon"> <div id="mobilizon">
<NavBar /> <NavBar />
<main class="container"> <main>
<router-view /> <router-view />
</main> </main>
<mobilizon-footer /> <mobilizon-footer />

View file

@ -1,5 +1,5 @@
<template> <template>
<footer class="footer"> <footer class="footer" ref="footer">
<mobilizon-logo :invert="true" class="logo" /> <mobilizon-logo :invert="true" class="logo" />
<img src="../assets/footer.png" :alt="$t('World map')" /> <img src="../assets/footer.png" :alt="$t('World map')" />
<ul> <ul>

View file

@ -125,6 +125,8 @@ export interface IEvent {
tags: ITag[]; tags: ITag[];
options: IEventOptions; options: IEventOptions;
toEditJSON();
} }
export interface IEventOptions { export interface IEventOptions {
@ -228,7 +230,7 @@ export class EventModel implements IEvent {
if (hash.options) this.options = hash.options; if (hash.options) this.options = hash.options;
} }
toEditJSON () { toEditJSON() {
return { return {
id: this.id, id: this.id,
title: this.title, title: this.title,

View file

@ -1,5 +1,5 @@
<template> <template>
<div> <section class="container">
<span v-if="code === ErrorCode.REGISTRATION_CLOSED"> <span v-if="code === ErrorCode.REGISTRATION_CLOSED">
{{ $t('Registration is currently closed.') }} {{ $t('Registration is currently closed.') }}
</span> </span>
@ -7,7 +7,7 @@
<span v-else> <span v-else>
{{ $t('Unknown error.') }} {{ $t('Unknown error.') }}
</span> </span>
</div> </section>
</template> </template>
<script lang="ts"> <script lang="ts">

View file

@ -1,172 +1,194 @@
import {EventJoinOptions} from "@/types/event.model";
<template> <template>
<section class="container"> <section>
<h1 class="title" v-if="isUpdate === false"> <div class="container">
{{ $t('Create a new event') }} <h1 class="title" v-if="isUpdate === false">
</h1> {{ $t('Create a new event') }}
<h1 class="title" v-else> </h1>
{{ $t('Update event {name}', { name: event.title }) }} <h1 class="title" v-else>
</h1> {{ $t('Update event {name}', { name: event.title }) }}
</h1>
<div v-if="$apollo.loading">{{ $t('Loading') }}</div> <div class="columns is-centered">
<form class="column is-two-thirds-desktop">
<h2 class="subtitle">
{{ $t('General information') }}
</h2>
<picture-upload v-model="pictureFile" />
<div class="columns is-centered" v-else> <b-field :label="$t('Title')" :type="checkTitleLength[0]" :message="checkTitleLength[1]">
<form class="column is-two-thirds-desktop" @submit="createOrUpdate"> <b-input aria-required="true" required v-model="event.title" />
<h2 class="subtitle"> </b-field>
{{ $t('General information') }}
</h2>
<picture-upload v-model="pictureFile" />
<b-field :label="$t('Title')" :type="checkTitleLength[0]" :message="checkTitleLength[1]"> <tag-input v-model="event.tags" :data="tags" path="title" />
<b-input aria-required="true" required v-model="event.title" />
</b-field>
<tag-input v-model="event.tags" :data="tags" path="title" /> <date-time-picker v-model="event.beginsOn" :label="$t('Starts on…')" :step="15"/>
<date-time-picker v-model="event.endsOn" :label="$t('Ends on…')" :step="15" />
<date-time-picker v-model="event.beginsOn" :label="$t('Starts on…')" :step="15"/> <address-auto-complete v-model="event.physicalAddress" />
<date-time-picker v-model="event.endsOn" :label="$t('Ends on…')" :step="15" />
<address-auto-complete v-model="event.physicalAddress" /> <b-field :label="$t('Organizer')">
<identity-picker-wrapper v-model="event.organizerActor"></identity-picker-wrapper>
</b-field>
<b-field :label="$t('Organizer')"> <div class="field">
<identity-picker-wrapper v-model="event.organizerActor"></identity-picker-wrapper> <label class="label">{{ $t('Description') }}</label>
</b-field> <editor v-model="event.description" />
</div>
<div class="field"> <b-field :label="$t('Website / URL')">
<label class="label">{{ $t('Description') }}</label> <b-input v-model="event.onlineAddress" placeholder="URL" />
<editor v-model="event.description" /> </b-field>
</div>
<b-field :label="$t('Website / URL')"> <!--<b-field :label="$t('Category')">
<b-input v-model="event.onlineAddress" placeholder="URL" /> <b-select placeholder="Select a category" v-model="event.category">
</b-field> <option
v-for="category in categories"
:value="category"
:key="category"
>{{ $t(category) }}</option>
</b-select>
</b-field>-->
<!--<b-field :label="$t('Category')"> <h2 class="subtitle">
<b-select placeholder="Select a category" v-model="event.category"> {{ $t('Who can view this event and participate') }}
<option </h2>
v-for="category in categories" <div class="field">
:value="category" <b-radio v-model="event.visibility"
:key="category" name="eventVisibility"
>{{ $t(category) }}</option> :native-value="EventVisibility.PUBLIC">
</b-select> {{ $t('Visible everywhere on the web (public)') }}
</b-field>--> </b-radio>
</div>
<h2 class="subtitle"> <div class="field">
{{ $t('Who can view this event and participate') }} <b-radio v-model="event.visibility"
</h2> name="eventVisibility"
:native-value="EventVisibility.UNLISTED">
{{ $t('Only accessible through link and search (private)') }}
</b-radio>
</div>
<div class="field"> <div class="field">
<b-radio v-model="event.visibility" <b-radio v-model="event.visibility"
name="eventVisibility" name="eventVisibility"
:native-value="EventVisibility.PUBLIC"> :native-value="EventVisibility.PRIVATE">
{{ $t('Visible everywhere on the web (public)') }} {{ $t('Page limited to my group (asks for auth)') }}
</b-radio> </b-radio>
</div> </div>
<div class="field"> <div class="field">
<b-radio v-model="event.visibility" <label class="label">{{ $t('Participation approval') }}</label>
name="eventVisibility" <b-switch v-model="needsApproval">
:native-value="EventVisibility.UNLISTED"> {{ $t('I want to approve every participation request') }}
{{ $t('Only accessible through link and search (private)') }} </b-switch>
</div>
<div class="field">
<b-switch v-model="limitedPlaces">
{{ $t('Limited places') }}
</b-switch>
</div>
<div class="box" v-if="limitedPlaces">
<b-field :label="$t('Number of places')">
<b-numberinput v-model="event.options.maximumAttendeeCapacity"></b-numberinput>
</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>
<h2 class="subtitle">
{{ $t('Public comment moderation') }}
</h2>
<label>{{ $t('Comments on the event page') }}</label>
<div class="field">
<b-radio v-model="event.options.commentModeration"
name="commentModeration"
:native-value="CommentModeration.ALLOW_ALL">
{{ $t('Allow all comments') }}
</b-radio> </b-radio>
</div> </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="field"> <div class="field">
<label class="label">{{ $t('Participation approval') }}</label> <b-radio v-model="event.options.commentModeration"
<b-switch v-model="needsApproval"> name="commentModeration"
{{ $t('I want to approve every participation request') }} :native-value="CommentModeration.MODERATED">
</b-switch> {{ $t('Moderated comments (shown after approval)') }}
</div> </b-radio>
</div>
<div class="field"> <div class="field">
<b-switch v-model="limitedPlaces"> <b-radio v-model="event.options.commentModeration"
{{ $t('Limited places') }} name="commentModeration"
</b-switch> :native-value="CommentModeration.CLOSED">
</div> {{ $t('Close comments for all (except for admins)') }}
</b-radio>
</div>
<div class="box" v-if="limitedPlaces"> <h2 class="subtitle">
<b-field :label="$t('Number of places')"> {{ $t('Status') }}
<b-numberinput v-model="event.options.maximumAttendeeCapacity"></b-numberinput> </h2>
</b-field>
<b-field> <div class="field">
<b-switch v-model="event.options.showRemainingAttendeeCapacity"> <b-radio v-model="event.status"
{{ $t('Show remaining number of places') }} name="status"
</b-switch> :native-value="EventStatus.TENTATIVE">
</b-field> {{ $t('Tentative: Will be confirmed later') }}
</b-radio>
</div>
<b-field> <div class="field">
<b-switch v-model="event.options.showParticipationPrice"> <b-radio v-model="event.status"
{{ $t('Display participation price') }} name="status"
</b-switch> :native-value="EventStatus.CONFIRMED">
</b-field> {{ $t('Confirmed: Will happen') }}
</div> </b-radio>
</div>
<h2 class="subtitle"> </form>
{{ $t('Public comment moderation') }} </div>
</h2>
<label>{{ $t('Comments on the event page') }}</label>
<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.MODERATED">
{{ $t('Moderated comments (shown after approval)') }}
</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>
<div class="field">
<b-radio v-model="event.status"
name="status"
:native-value="EventStatus.TENTATIVE">
{{ $t('Tentative: Will be confirmed later') }}
</b-radio>
</div>
<div class="field">
<b-radio v-model="event.status"
name="status"
:native-value="EventStatus.CONFIRMED">
{{ $t('Confirmed: Will happen') }}
</b-radio>
</div>
<button class="button is-primary">
<span v-if="isUpdate === false">{{ $t('Create my event') }}</span>
<span v-else> {{ $t('Update my event') }}</span>
</button>
</form>
</div> </div>
<span ref="bottomObserver"></span>
<nav role="navigation" aria-label="main navigation" class="navbar" :class="{'is-fixed-bottom': showFixedNavbar }">
<div class="container">
<div class="navbar-menu">
<div class="navbar-start">
<span class="navbar-item" v-if="isUpdate === true && isEventModified">{{ $t('Unsaved changes') }}</span>
</div>
<div class="navbar-end">
<span class="navbar-item">
<b-button type="is-text" @click="confirmGoBack">
{{ $t('Cancel') }}
</b-button>
</span>
<span class="navbar-item" v-if="isUpdate === false">
<b-button type="is-primary" outlined>
{{ $t('Save draft') }}
</b-button>
</span>
<span class="navbar-item">
<b-button type="is-primary" @click="createOrUpdate" @keyup.enter="createOrUpdate">
<span v-if="isUpdate === false">{{ $t('Create my event') }}</span>
<span v-else> {{ $t('Update my event') }}</span>
</b-button>
</span>
</div>
</div>
</div>
</nav>
</section> </section>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
@import "@/variables.scss"; @import "@/variables.scss";
h2.subtitle { h2.subtitle {
@ -178,17 +200,43 @@ import {EventJoinOptions} from "@/types/event.model";
background: $secondary; background: $secondary;
} }
} }
section {
& > .container {
margin-bottom: 2rem;
padding: 1rem;
}
nav.navbar {
min-height: 2rem !important;
background: lighten($secondary, 10%);
.container {
min-height: 2rem;
.navbar-menu, .navbar-end {
display: flex !important;
background: lighten($secondary, 10%);
}
.navbar-end {
justify-content: flex-end;
margin-left: auto;
}
}
}
}
</style> </style>
<script lang="ts"> <script lang="ts">
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event'; import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { import {
CommentModeration, EventJoinOptions, CommentModeration, EventJoinOptions,
EventModel, EventModel,
EventStatus, EventStatus,
EventVisibility, EventVisibility, IEvent,
} from '@/types/event.model'; } from '@/types/event.model';
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor'; import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
import { Person } from '@/types/actor'; import { Person } from '@/types/actor';
import PictureUpload from '@/components/PictureUpload.vue'; import PictureUpload from '@/components/PictureUpload.vue';
@ -220,7 +268,8 @@ export default class EditEvent extends Vue {
currentActor = new Person(); currentActor = new Person();
tags: ITag[] = []; tags: ITag[] = [];
event = new EventModel(); event: IEvent = new EventModel();
unmodifiedEvent!: IEvent;
pictureFile: File | null = null; pictureFile: File | null = null;
EventStatus = EventStatus; EventStatus = EventStatus;
@ -229,6 +278,8 @@ export default class EditEvent extends Vue {
canPromote: boolean = true; canPromote: boolean = true;
limitedPlaces: boolean = false; limitedPlaces: boolean = false;
CommentModeration = CommentModeration; CommentModeration = CommentModeration;
showFixedNavbar: boolean = true;
observer!: IntersectionObserver;
// categories: string[] = Object.keys(Category); // categories: string[] = Object.keys(Category);
@ -240,6 +291,7 @@ export default class EditEvent extends Vue {
if (this.eventId) { if (this.eventId) {
this.event = await this.getEvent(); this.event = await this.getEvent();
this.unmodifiedEvent = JSON.parse(JSON.stringify(this.event));
this.pictureFile = await buildFileFromIPicture(this.event.picture); this.pictureFile = await buildFileFromIPicture(this.event.picture);
this.limitedPlaces = this.event.options.maximumAttendeeCapacity != null; this.limitedPlaces = this.event.options.maximumAttendeeCapacity != null;
@ -256,6 +308,20 @@ export default class EditEvent extends Vue {
this.event.organizerActor = this.event.organizerActor || this.currentActor; this.event.organizerActor = this.event.organizerActor || this.currentActor;
} }
mounted() {
this.observer = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (entry) {
console.log(entry);
this.showFixedNavbar = !entry.isIntersecting;
}
}
}, {
rootMargin: '-50px 0px -50px',
});
this.observer.observe(this.$refs.bottomObserver as Element);
}
createOrUpdate(e: Event) { createOrUpdate(e: Event) {
e.preventDefault(); e.preventDefault();
@ -342,6 +408,34 @@ export default class EditEvent extends Vue {
return this.event.title.length > 80 ? ['is-info', this.$t('The event title will be ellipsed.')] : [undefined, undefined]; return this.event.title.length > 80 ? ['is-info', this.$t('The event title will be ellipsed.')] : [undefined, undefined];
} }
/**
* Confirm cancel
*/
confirmGoBack() {
const title: string = this.isUpdate ?
this.$t('Cancel edition') as string :
this.$t('Cancel creation') as string;
const message: string = this.isUpdate ?
this.$t('Are you sure you want to cancel the event edition? You\'ll lose all modifications.',
{ title: this.event.title }) as string :
this.$t('Are you sure you want to cancel the event creation? You\'ll lose all modifications.',
{ title: this.event.title }) as string;
this.$buefy.dialog.confirm({
title,
message,
confirmText: this.$t('Cancel') as string,
cancelText: this.$t('Continue editing') as string,
type: 'is-warning',
hasIcon: true,
onConfirm: () => this.$router.go(-1),
});
}
get isEventModified(): boolean {
return JSON.stringify(this.event) !== JSON.stringify(this.unmodifiedEvent);
}
// getAddressData(addressData) { // getAddressData(addressData) {
// if (addressData !== null) { // if (addressData !== null) {
// this.event.address = { // this.event.address = {

View file

@ -1,8 +1,5 @@
import {ParticipantRole} from "@/types/event.model";
import {ParticipantRole} from "@/types/event.model";
import {ParticipantRole} from "@/types/event.model";
<template> <template>
<div> <div class="container">
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="event"> <div v-if="event">
<div class="header-picture container"> <div class="header-picture container">

View file

@ -1,5 +1,5 @@
<template> <template>
<section> <section class="container">
<h1> <h1>
{{ $t('Event list') }} {{ $t('Event list') }}
</h1> </h1>

View file

@ -1,5 +1,5 @@
<template> <template>
<section> <section class="container">
<h1 class="title">{{ $t('Explore') }}</h1> <h1 class="title">{{ $t('Explore') }}</h1>
<!-- <pre>{{ events }}</pre>--> <!-- <pre>{{ events }}</pre>-->
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>

View file

@ -1,5 +1,5 @@
<template> <template>
<main> <main class="container">
<h1 class="title"> <h1 class="title">
{{ $t('My events') }} {{ $t('My events') }}
</h1> </h1>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="root"> <div class="container root">
<h1>{{ $t('Create a new group') }}</h1> <h1>{{ $t('Create a new group') }}</h1>
<div> <div>

View file

@ -1,5 +1,5 @@
<template> <template>
<section> <section class="container">
<h1> <h1>
{{ $t('Group List') }} {{ $t('Group List') }}
</h1> </h1>

View file

@ -1,4 +1,3 @@
import {ReportStatusEnum} from "@/types/report.model";
<template> <template>
<section class="container"> <section class="container">
<nav class="breadcrumb" aria-label="breadcrumbs"> <nav class="breadcrumb" aria-label="breadcrumbs">

View file

@ -1,4 +1,3 @@
import {ReportStatusEnum} from "@/types/report.model";
<template> <template>
<section class="container"> <section class="container">
<nav class="breadcrumb" aria-label="breadcrumbs"> <nav class="breadcrumb" aria-label="breadcrumbs">

View file

@ -1,5 +1,5 @@
<template> <template>
<section class="section"> <section class="container">
<nav class="breadcrumb" aria-label="breadcrumbs"> <nav class="breadcrumb" aria-label="breadcrumbs">
<ul> <ul>
<li><router-link :to="{ name: MyAccountRouteName.UPDATE_IDENTITY }">{{ $t('My account') }}</router-link></li> <li><router-link :to="{ name: MyAccountRouteName.UPDATE_IDENTITY }">{{ $t('My account') }}</router-link></li>

View file

@ -1,5 +1,5 @@
<template> <template>
<section class="columns is-mobile is-centered"> <section class="container columns is-mobile is-centered">
<div class="card column is-half-desktop"> <div class="card column is-half-desktop">
<h1> <h1>
{{ $t('Password reset') }} {{ $t('Password reset') }}

View file

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="container">
<section class="hero"> <section class="hero">
<div class="hero-body"> <div class="hero-body">
<h1 class="title"> <h1 class="title">

View file

@ -1,5 +1,5 @@
<template> <template>
<section> <section class="container">
<h1 class="title" v-if="loading"> <h1 class="title" v-if="loading">
{{ $t('Your account is being validated') }} {{ $t('Your account is being validated') }}
</h1> </h1>