Various UI stuff (mainly implement mookup)
Fix lint Disable modern mode Fixes UI fixes Fixes Ignore .po~ files Fixes Fix homepage Fixes Fixes Mix format Fix tests Fix tests (yeah…) Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -1,105 +1,101 @@
|
||||
<template>
|
||||
<section>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card" v-if="person">
|
||||
<div class="card-image" v-if="person.bannerUrl">
|
||||
<figure class="image">
|
||||
<img :src="person.bannerUrl">
|
||||
<section class="container">
|
||||
<div v-if="person">
|
||||
<div class="card-image" v-if="person.bannerUrl">
|
||||
<figure class="image">
|
||||
<img :src="person.bannerUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="person.avatarUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="person.avatarUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title">{{ person.name }}</p>
|
||||
<p class="subtitle">@{{ person.preferredUsername }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p v-html="person.summary"></p>
|
||||
</div>
|
||||
|
||||
<b-dropdown hoverable has-link aria-role="list">
|
||||
<button class="button is-info" slot="trigger">
|
||||
<translate>Public feeds</translate>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('atom', true)">
|
||||
<translate>Public RSS/Atom Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('ics', true)">
|
||||
<translate>Public iCal Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-dropdown hoverable has-link aria-role="list" v-if="person.feedTokens.length > 0">
|
||||
<button class="button is-info" slot="trigger">
|
||||
<translate>Private feeds</translate>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('atom', false)">
|
||||
<translate>RSS/Atom Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('ics', false)">
|
||||
<translate>iCal Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<a class="button" v-else @click="createToken">
|
||||
<translate>Create token</translate>
|
||||
</a>
|
||||
<div class="media-content">
|
||||
<p class="title">{{ person.name }}</p>
|
||||
<p class="subtitle">@{{ person.preferredUsername }}</p>
|
||||
</div>
|
||||
<section v-if="person.organizedEvents.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Organized</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<EventCard
|
||||
v-for="event in person.organizedEvents"
|
||||
:event="event"
|
||||
:options="{ hideDetails: true }"
|
||||
:key="event.uuid"
|
||||
class="column is-one-third"
|
||||
/>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<p class="control">
|
||||
<a
|
||||
class="button"
|
||||
@click="logoutUser()"
|
||||
v-if="loggedPerson && loggedPerson.id === person.id"
|
||||
>
|
||||
<translate>User logout</translate>
|
||||
</a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a
|
||||
class="button"
|
||||
@click="deleteProfile()"
|
||||
v-if="loggedPerson && loggedPerson.id === person.id"
|
||||
>
|
||||
<translate>Delete</translate>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<vue-simple-markdown :source="person.summary"></vue-simple-markdown>
|
||||
</div>
|
||||
|
||||
<b-dropdown hoverable has-link aria-role="list">
|
||||
<button class="button is-primary" slot="trigger">
|
||||
<translate>Public feeds</translate>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('atom', true)">
|
||||
<translate>Public RSS/Atom Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('ics', true)">
|
||||
<translate>Public iCal Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
|
||||
<b-dropdown hoverable has-link aria-role="list" v-if="person.feedTokens.length > 0">
|
||||
<button class="button is-info" slot="trigger">
|
||||
<translate>Private feeds</translate>
|
||||
<b-icon icon="menu-down"></b-icon>
|
||||
</button>
|
||||
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('atom', false)">
|
||||
<translate>RSS/Atom Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item aria-role="listitem">
|
||||
<a :href="feedUrls('ics', false)">
|
||||
<translate>iCal Feed</translate>
|
||||
</a>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<a class="button" v-else-if="loggedPerson" @click="createToken">
|
||||
<translate>Create token</translate>
|
||||
</a>
|
||||
</div>
|
||||
<section v-if="person.organizedEvents.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Organized</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<EventCard
|
||||
v-for="event in person.organizedEvents"
|
||||
:event="event"
|
||||
:options="{ hideDetails: true, organizerActor: person }"
|
||||
:key="event.uuid"
|
||||
class="column is-one-third"
|
||||
/>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<p class="control">
|
||||
<a
|
||||
class="button"
|
||||
@click="logoutUser()"
|
||||
v-if="loggedPerson && loggedPerson.id === person.id"
|
||||
>
|
||||
<translate>User logout</translate>
|
||||
</a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a
|
||||
class="button"
|
||||
@click="deleteProfile()"
|
||||
v-if="loggedPerson && loggedPerson.id === person.id"
|
||||
>
|
||||
<translate>Delete</translate>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -172,3 +168,8 @@ export default class Profile extends Vue {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "../../variables";
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
@import "~bulma/sass/components/dropdown.sass";
|
||||
</style>
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { ErrorCode } from '@/types/error-code.model';
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { ErrorCode } from '@/types/error-code.model';
|
||||
|
||||
@Component
|
||||
export default class ErrorPage extends Vue {
|
||||
code: ErrorCode | null = null;
|
||||
@Component
|
||||
export default class ErrorPage extends Vue {
|
||||
code: ErrorCode | null = null;
|
||||
|
||||
ErrorCode = ErrorCode;
|
||||
ErrorCode = ErrorCode;
|
||||
|
||||
mounted() {
|
||||
this.code = this.$route.query[ 'code' ] as ErrorCode;
|
||||
}
|
||||
mounted() {
|
||||
this.code = this.$route.query['code'] as ErrorCode;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -95,7 +95,7 @@ export default class CreateEvent extends Vue {
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,258 @@
|
||||
<template>
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-three-quarters">
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div class="card" v-if="event">
|
||||
<div class="card-image">
|
||||
<figure class="image is-4by3">
|
||||
<img src="https://picsum.photos/600/400/">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span>{{ event.beginsOn | formatDay }}</span>
|
||||
<span class="tag is-primary">{{ event.category }}</span>
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
<router-link
|
||||
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"
|
||||
>
|
||||
<figure v-if="event.organizerActor.avatarUrl">
|
||||
<img :src="event.organizerActor.avatarUrl">
|
||||
</figure>
|
||||
</router-link>
|
||||
<span
|
||||
v-if="event.organizerActor"
|
||||
>Organisé par {{ event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername }}</span>
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<router-link
|
||||
v-if="actorIsOrganizer()"
|
||||
class="button"
|
||||
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
|
||||
>
|
||||
<translate>Edit</translate>
|
||||
</router-link>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button" @click="downloadIcsEvent()">
|
||||
<translate>Download</translate>
|
||||
<div>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-if="event">
|
||||
<div class="header-picture container">
|
||||
<figure class="image is-3by1">
|
||||
<img src="https://picsum.photos/600/200/">
|
||||
</figure>
|
||||
</div>
|
||||
<section class="container">
|
||||
<div class="title-and-participate-button">
|
||||
<div class="title-wrapper">
|
||||
<div class="date-component">
|
||||
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
||||
</div>
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
</div>
|
||||
<div v-if="!actorIsOrganizer()" class="participate-button has-text-centered">
|
||||
<a v-if="!actorIsParticipant()" @click="joinEvent" class="button is-large is-primary is-rounded">
|
||||
<b-icon icon="circle-outline"></b-icon>
|
||||
<translate>Join</translate>
|
||||
</a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button is-danger" v-if="actorIsOrganizer()" @click="deleteEvent()">
|
||||
<translate>Delete</translate>
|
||||
<a v-if="actorIsParticipant()" @click="leaveEvent" class="button is-large is-primary is-rounded">
|
||||
<b-icon icon="check-circle"></b-icon>
|
||||
<translate>Leave</translate>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ event.beginsOn | formatDate }} - {{ event.endsOn | formatDate }}</span>
|
||||
</div>
|
||||
<div class="address" v-if="event.physicalAddress">
|
||||
<h3 class="subtitle">Adresse</h3>
|
||||
<address>
|
||||
<span>{{ event.physicalAddress.description }}</span><br>
|
||||
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span><br>
|
||||
<span>{{ event.physicalAddress.postal_code }} {{ event.physicalAddress.locality }}</span><br>
|
||||
<span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>
|
||||
</address>
|
||||
<div class="map">
|
||||
<map-leaflet
|
||||
:coords="event.physicalAddress.geom"
|
||||
:popup="event.physicalAddress.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="actorIsOrganizer()">
|
||||
<translate>You are an organizer.</translate>
|
||||
</p>
|
||||
<div v-else>
|
||||
<p v-if="actorIsParticipant()">
|
||||
<translate>You announced that you're going to this event.</translate>
|
||||
</p>
|
||||
<p v-else>
|
||||
<translate>Are you going to this event?</translate><br />
|
||||
<span>
|
||||
<translate
|
||||
:translate-n="event.participants.length"
|
||||
translate-plural="%{event.participants.length} persons are going"
|
||||
>
|
||||
One person is going.
|
||||
</translate>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!actorIsOrganizer()">
|
||||
<a v-if="!actorIsParticipant()" @click="joinEvent" class="button">
|
||||
<translate>Join</translate>
|
||||
</a>
|
||||
<a v-if="actorIsParticipant()" @click="leaveEvent" class="button">Leave</a>
|
||||
</div>
|
||||
<h2 class="subtitle">Details</h2>
|
||||
<p v-if="event.description">
|
||||
<vue-simple-markdown :source="event.description"></vue-simple-markdown>
|
||||
</p>
|
||||
<h2 class="subtitle">Participants</h2>
|
||||
<span v-if="event.participants.length === 0">No participants yet.</span>
|
||||
<div class="columns">
|
||||
<router-link
|
||||
class="card column"
|
||||
v-for="participant in event.participants"
|
||||
:key="participant.preferredUsername"
|
||||
:to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"
|
||||
>
|
||||
<div>
|
||||
<figure>
|
||||
<img v-if="!participant.actor.avatarUrl" src="https://picsum.photos/125/125/">
|
||||
<img v-else :src="participant.actor.avatarUrl">
|
||||
</figure>
|
||||
<span>{{ participant.actor.preferredUsername }}</span>
|
||||
<div class="metadata columns">
|
||||
<div class="column is-three-quarters-desktop">
|
||||
<p class="tags" v-if="event.category || event.tags.length > 0">
|
||||
<span class="tag" v-if="event.category">{{ event.category }}</span>
|
||||
<span class="tag" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</span>
|
||||
<span class="visibility">
|
||||
<translate v-if="event.visibility === EventVisibility.PUBLIC">public event</translate>
|
||||
</span>
|
||||
</p>
|
||||
<div class="date-and-add-to-calendar">
|
||||
<div class="date-and-privacy" v-if="event.beginsOn">
|
||||
<b-icon icon="calendar-clock" />
|
||||
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
|
||||
</div>
|
||||
<a class="add-to-calendar" @click="downloadIcsEvent()">
|
||||
<b-icon icon="calendar-plus" />
|
||||
<translate>Add to my calendar</translate>
|
||||
</a>
|
||||
</div>
|
||||
</router-link>
|
||||
<p class="slug">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
In aliquam libero quam, ut ultricies velit porttitor a. Maecenas mollis vestibulum dolor.
|
||||
</p>
|
||||
</div>
|
||||
<div class="column sidebar">
|
||||
<div class="field has-addons" v-if="actorIsOrganizer()">
|
||||
<p class="control">
|
||||
<router-link
|
||||
class="button"
|
||||
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
|
||||
>
|
||||
<translate>Edit</translate>
|
||||
</router-link>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button is-danger" @click="deleteEvent()">
|
||||
<translate>Delete</translate>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="address-wrapper">
|
||||
<b-icon icon="map" />
|
||||
<translate v-if="!event.physicalAddress">No address defined</translate>
|
||||
<div class="address" v-if="event.physicalAddress">
|
||||
<address>
|
||||
<span class="addressDescription">{{ event.physicalAddress.description }}</span>
|
||||
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
|
||||
<span>{{ event.physicalAddress.postal_code }} {{ event.physicalAddress.locality }}</span>
|
||||
<!-- <span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>-->
|
||||
</address>
|
||||
<span class="map-show-button" @click="showMap = !showMap">
|
||||
<translate>Show map</translate>
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="map" v-if="showMap">-->
|
||||
<!-- <map-leaflet-->
|
||||
<!-- :coords="event.physicalAddress.geom"-->
|
||||
<!-- :popup="event.physicalAddress.description"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<b-modal v-if="event.physicalAddress" :active.sync="showMap" :width="800" scroll="keep">
|
||||
<div class="map">
|
||||
<map-leaflet
|
||||
:coords="event.physicalAddress.geom"
|
||||
:popup="event.physicalAddress.description"
|
||||
/>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
<div class="organizer">
|
||||
<router-link
|
||||
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"
|
||||
>
|
||||
<translate
|
||||
:translate-params="{name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}"
|
||||
v-if="event.organizerActor">By %{ name }</translate>
|
||||
<figure v-if="event.organizerActor.avatarUrl" class="image is-48x48">
|
||||
<img
|
||||
class="is-rounded"
|
||||
:src="event.organizerActor.avatarUrl"
|
||||
:alt="$gettextInterpolate('%{actor}\'s avatar', {actor: event.organizerActor.preferredUsername})" />
|
||||
</figure>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- <p v-if="actorIsOrganizer()">-->
|
||||
<!-- <translate>You are an organizer.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <div v-else>-->
|
||||
<!-- <p v-if="actorIsParticipant()">-->
|
||||
<!-- <translate>You announced that you're going to this event.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <p v-else>-->
|
||||
<!-- <translate>Are you going to this event?</translate><br />-->
|
||||
<!-- <span>-->
|
||||
<!-- <translate-->
|
||||
<!-- :translate-n="event.participants.length"-->
|
||||
<!-- translate-plural="%{event.participants.length} persons are going"-->
|
||||
<!-- >-->
|
||||
<!-- One person is going.-->
|
||||
<!-- </translate>-->
|
||||
<!-- </span>-->
|
||||
<!-- </p>-->
|
||||
<!-- </div>-->
|
||||
<div class="description">
|
||||
<div class="description-container container">
|
||||
<h3 class="title">
|
||||
<translate>About this event</translate>
|
||||
</h3>
|
||||
<p v-if="!event.description">
|
||||
<translate>The event organizer didn't add any description.</translate>
|
||||
</p>
|
||||
<div class="columns" v-else="event.description">
|
||||
<div class="column is-half">
|
||||
<!-- <vue-simple-markdown :source="event.description" />-->
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Suspendisse vehicula ex dapibus augue volutpat, ultrices cursus mi rutrum.
|
||||
Nunc ante nunc, facilisis a tellus quis, tempor mollis diam. Aenean consectetur quis est a ultrices.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</p>
|
||||
<p><a href="https://framasoft.org">https://framasoft.org</a>
|
||||
<p>
|
||||
Nam sit amet est eget velit tristique commodo. Etiam sollicitudin dignissim diam, ut ultricies tortor.
|
||||
Sed quis blandit diam, a tincidunt nunc. Donec tincidunt tristique neque at rhoncus. Ut eget vulputate felis.
|
||||
Pellentesque nibh purus, viverra ac augue sed, iaculis feugiat velit. Nulla ut hendrerit elit.
|
||||
Etiam at justo eu nunc tempus sagittis. Sed ac tincidunt tellus, sit amet luctus velit.
|
||||
Nam ullamcorper eros eleifend, eleifend diam vitae, lobortis risus.
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
Curabitur rhoncus sapien tortor, vitae imperdiet massa scelerisque non.
|
||||
Aliquam eu augue mi. Donec hendrerit lorem orci.
|
||||
</em>
|
||||
</p>
|
||||
<p>
|
||||
Donec volutpat, enim eu laoreet dictum, urna quam varius enim, eu convallis urna est vitae massa.
|
||||
Morbi porttitor lacus a sem efficitur blandit. Mauris in est in quam tincidunt iaculis non vitae ipsum.
|
||||
Phasellus eget velit tellus. Curabitur ac neque pharetra velit viverra mollis.
|
||||
</p>
|
||||
<img src="https://framasoft.org/img/biglogo-notxt.png" alt="logo Framasoft"/>
|
||||
<p>Aenean gravida, ante vitae aliquet aliquet, elit quam tristique orci, sit amet dictum lorem ipsum nec tortor.
|
||||
Vestibulum est eros, faucibus et semper vel, dapibus ac est. Suspendisse potenti. Suspendisse potenti.
|
||||
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
Nulla molestie nisi ac risus hendrerit, dapibus mattis sapien scelerisque.
|
||||
</p>
|
||||
<p>Maecenas id pretium justo, nec dignissim sapien. Mauris in venenatis odio, in congue augue. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <section class="container">-->
|
||||
<!-- <h2 class="title">Participants</h2>-->
|
||||
<!-- <span v-if="event.participants.length === 0">No participants yet.</span>-->
|
||||
<!-- <div class="columns">-->
|
||||
<!-- <router-link-->
|
||||
<!-- class="column"-->
|
||||
<!-- v-for="participant in event.participants"-->
|
||||
<!-- :key="participant.preferredUsername"-->
|
||||
<!-- :to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"-->
|
||||
<!-- >-->
|
||||
<!-- <div>-->
|
||||
<!-- <figure>-->
|
||||
<!-- <img v-if="!participant.actor.avatarUrl" src="https://picsum.photos/125/125/">-->
|
||||
<!-- <img v-else :src="participant.actor.avatarUrl">-->
|
||||
<!-- </figure>-->
|
||||
<!-- <span>{{ participant.actor.preferredUsername }}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </router-link>-->
|
||||
<!-- </div>-->
|
||||
<!-- </section>-->
|
||||
<section class="share">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-half has-text-centered">
|
||||
<h3 class="title"><translate>Share this event</translate></h3>
|
||||
<b-icon icon="mastodon" size="is-large" type="is-primary" />
|
||||
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
|
||||
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
|
||||
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="email" size="is-large" type="is-primary" /></a>
|
||||
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
|
||||
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="column is-half has-text-right add-to-calendar">
|
||||
<h3 @click="downloadIcsEvent()">
|
||||
<translate>Add to my calendar</translate>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="more-events container">
|
||||
<h3 class="title has-text-centered"><translate>These events may interest you</translate></h3>
|
||||
<div class="columns">
|
||||
<div class="column" v-for="index in 3" :key="index">
|
||||
<EventCard :event="event" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</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 { LOGGED_PERSON } from '@/graphql/actor';
|
||||
import { IEvent, IParticipant } from '@/types/event.model';
|
||||
import { EventVisibility, IEvent, IParticipant } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor.model';
|
||||
import { RouteName } from '@/router';
|
||||
import 'vue-simple-markdown/dist/vue-simple-markdown.css';
|
||||
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
|
||||
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
|
||||
import BIcon from 'buefy/src/components/icon/Icon.vue';
|
||||
import EventCard from '@/components/Event/EventCard.vue';
|
||||
import EventFullDate from '@/components/Event/EventFullDate.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
EventFullDate,
|
||||
EventCard,
|
||||
BIcon,
|
||||
DateCalendarIcon,
|
||||
'map-leaflet': () => import('@/components/Map.vue'),
|
||||
},
|
||||
apollo: {
|
||||
@@ -148,6 +275,9 @@ export default class Event extends Vue {
|
||||
event!: IEvent;
|
||||
loggedPerson!: IPerson;
|
||||
validationSent: boolean = false;
|
||||
showMap: boolean = false;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
|
||||
async deleteEvent() {
|
||||
const router = this.$router;
|
||||
@@ -241,12 +371,255 @@ export default class Event extends Vue {
|
||||
return this.loggedPerson &&
|
||||
this.loggedPerson.id === this.event.organizerActor.id;
|
||||
}
|
||||
|
||||
get twitterShareUrl(): string {
|
||||
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.event.url)}&text=${this.event.title}`;
|
||||
}
|
||||
|
||||
get facebookShareUrl(): string {
|
||||
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(this.event.url)}`;
|
||||
}
|
||||
|
||||
get linkedInShareUrl(): string {
|
||||
return `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(this.event.url)}&title=${this.event.title}`;
|
||||
}
|
||||
|
||||
get emailShareUrl(): string {
|
||||
return `mailto:?to=&body=${this.event.url}${encodeURIComponent('\n\n')}${this.event.description}&subject=${this.event.title}`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.address div.map {
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
padding: 25px 35px;
|
||||
<style lang="scss" scoped>
|
||||
@import "../../variables";
|
||||
|
||||
div.sidebar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
background: #B3B3B2;
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
top: 30px;
|
||||
left: 0;
|
||||
height: calc(100% - 60px);
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
div.address-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
|
||||
div.address {
|
||||
flex: 1;
|
||||
|
||||
.map-show-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
address {
|
||||
font-style: normal;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
span.addressDescription {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 0 auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
:not(.addressDescription) {
|
||||
color: rgba(46, 62, 72, .6);
|
||||
flex: 1;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.map {
|
||||
height: 900px;
|
||||
width: 100%;
|
||||
padding: 25px 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
div.organizer {
|
||||
display: inline-flex;
|
||||
padding-top: 10px;
|
||||
|
||||
a {
|
||||
color: #4a4a4a;
|
||||
|
||||
span {
|
||||
line-height: 2.7rem;
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.title-and-participate-button {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/*flex-flow: row wrap;*/
|
||||
justify-content: space-between;
|
||||
/*align-self: center;*/
|
||||
align-items: stretch;
|
||||
/*align-content: space-around;*/
|
||||
padding: 15px 10px 0;
|
||||
|
||||
div.title-wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
|
||||
|
||||
div.date-component {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
font-weight: normal;
|
||||
word-break: break-word;
|
||||
font-size: 1.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.participate-button {
|
||||
flex: 0 1 auto;
|
||||
display: inline-flex;
|
||||
|
||||
a.button {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.metadata {
|
||||
padding: 0 10px;
|
||||
|
||||
div.date-and-add-to-calendar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
span.icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.date-and-privacy {
|
||||
color: $primary;
|
||||
padding: 0.3rem;
|
||||
background: $secondary;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.add-to-calendar {
|
||||
flex: 0 0 auto;
|
||||
margin-left: 10px;
|
||||
color: #484849;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.tags {
|
||||
span {
|
||||
&.tag {
|
||||
&::before {
|
||||
content: '#';
|
||||
}
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.visibility::before {
|
||||
content: "⋅"
|
||||
}
|
||||
|
||||
|
||||
margin: auto 5px;
|
||||
}
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3.title {
|
||||
font-size: 3rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding-top: 10px;
|
||||
min-height: 40rem;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 800px;
|
||||
background-position: 95% 101%;
|
||||
background-image: url('../../assets/texting.svg');
|
||||
border-top: solid 1px #111;
|
||||
border-bottom: solid 1px #111;
|
||||
|
||||
p {
|
||||
margin: 10px auto;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 0.3rem;
|
||||
background: $secondary;
|
||||
color: #111;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share {
|
||||
border-bottom: solid 1px #111;
|
||||
|
||||
.columns {
|
||||
|
||||
& > * {
|
||||
padding: 10rem 0;
|
||||
}
|
||||
|
||||
.add-to-calendar {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 400px;
|
||||
background-position: 10% 50%;
|
||||
background-image: url('../../assets/undraw_events.svg');
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content:"";
|
||||
background: #B3B3B2;
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
height: 40%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
display: block;
|
||||
color: $primary;
|
||||
font-size: 3rem;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: $secondary;
|
||||
cursor: pointer;
|
||||
max-width: 20rem;
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-events {
|
||||
margin: 50px auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,76 +1,73 @@
|
||||
<template>
|
||||
<section>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card" v-if="group">
|
||||
<div class="card-image" v-if="group.bannerUrl">
|
||||
<figure class="image">
|
||||
<img :src="group.bannerUrl">
|
||||
<section class="container">
|
||||
<div v-if="group">
|
||||
<div class="card-image" v-if="group.bannerUrl">
|
||||
<figure class="image">
|
||||
<img :src="group.bannerUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="group.avatarUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="group.avatarUrl">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title">{{ group.name }}</p>
|
||||
<p class="subtitle">@{{ group.preferredUsername }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p v-html="group.summary"></p>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title">{{ group.name }}</p>
|
||||
<p class="subtitle">@{{ group.preferredUsername }}</p>
|
||||
</div>
|
||||
<section v-if="group.organizedEvents.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Organized</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<EventCard
|
||||
v-for="event in group.organizedEvents"
|
||||
:event="event"
|
||||
:options="{ hideDetails: true }"
|
||||
:key="event.uuid"
|
||||
class="column is-one-third"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="group.members.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Members</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<span
|
||||
v-for="member in group.members"
|
||||
:key="member"
|
||||
>{{ member.actor.preferredUsername }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<b-message v-if-else="!group && $apollo.loading === false" type="is-danger">
|
||||
<translate>No group found</translate>
|
||||
</b-message>
|
||||
|
||||
<div class="content">
|
||||
<p v-html="group.summary"></p>
|
||||
</div>
|
||||
</div>
|
||||
<section class="box" v-if="group.organizedEvents.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Organized</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<EventCard
|
||||
v-for="event in group.organizedEvents"
|
||||
:event="event"
|
||||
:options="{ hideDetails: true }"
|
||||
:key="event.uuid"
|
||||
class="column is-one-third"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="group.members.length > 0">
|
||||
<h2 class="subtitle">
|
||||
<translate>Members</translate>
|
||||
</h2>
|
||||
<div class="columns">
|
||||
<span
|
||||
v-for="member in group.members"
|
||||
:key="member.actor.preferredUsername"
|
||||
>{{ member.actor.preferredUsername }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<b-message v-else-if="!group && $apollo.loading === false" type="is-danger">
|
||||
<translate>No group found</translate>
|
||||
</b-message>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import EventCard from '@/components/Event/EventCard.vue';
|
||||
import { FETCH_PERSON, LOGGED_PERSON } from '@/graphql/actor';
|
||||
import { FETCH_GROUP, LOGGED_PERSON } from '@/graphql/actor';
|
||||
import { IGroup } from '@/types/actor.model';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
person: {
|
||||
query: FETCH_PERSON,
|
||||
group: {
|
||||
query: FETCH_GROUP,
|
||||
variables() {
|
||||
return {
|
||||
name: this.$route.params.name,
|
||||
name: this.$route.params.preferredUsername,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -83,9 +80,9 @@ import { FETCH_PERSON, LOGGED_PERSON } from '@/graphql/actor';
|
||||
},
|
||||
})
|
||||
export default class Group extends Vue {
|
||||
@Prop({ type: String, required: true }) name!: string;
|
||||
@Prop({ type: String, required: true }) preferredUsername!: string;
|
||||
|
||||
group = null;
|
||||
group!: IGroup;
|
||||
loading = true;
|
||||
|
||||
created() {
|
||||
@@ -110,3 +107,8 @@ export default class Group extends Vue {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
section.container {
|
||||
min-height: 30em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
class="column is-one-quarter-desktop is-half-mobile"
|
||||
/>
|
||||
</div>
|
||||
<router-link class="button" :to="{ name: 'CreateGroup' }">
|
||||
<router-link class="button" :to="{ name: RouteName.CREATE_GROUP }">
|
||||
<translate>Create group</translate>
|
||||
</router-link>
|
||||
</section>
|
||||
@@ -27,6 +27,8 @@ export default class GroupList extends Vue {
|
||||
groups = [];
|
||||
loading = true;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
created() {
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
<section class="hero is-link" v-if="!currentUser.id || !loggedPerson">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title">Find events you like</h1>
|
||||
<h2 class="subtitle">Share them with Mobilizon</h2>
|
||||
<router-link class="button" :to="{ name: 'Register' }">
|
||||
<h1 class="title">{{ config.name }}</h1>
|
||||
<h2 class="subtitle">{{ config.description }}</h2>
|
||||
<router-link class="button" :to="{ name: 'Register' }" v-if="config.registrationsOpen">
|
||||
<translate>Register</translate>
|
||||
</router-link>
|
||||
<p v-else>
|
||||
<translate>This instance isn't opened to registrations, but you can register on other instances.</translate>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -18,8 +21,8 @@
|
||||
>Welcome back %{username}</translate>
|
||||
</h1>
|
||||
</section>
|
||||
<section v-if="loggedPerson">
|
||||
<span class="events-nearby title">Events you're going at</span>
|
||||
<section v-if="loggedPerson" class="container">
|
||||
<span class="events-nearby title"><translate>Events you're going at</translate></span>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-if="goingToEvents.size > 0" v-for="row in Array.from(goingToEvents.entries())">
|
||||
<!-- Iterators will be supported in v-for with VueJS 3 -->
|
||||
@@ -62,16 +65,15 @@
|
||||
<translate>You're not going to any event yet</translate>
|
||||
</b-message>
|
||||
</section>
|
||||
<section>
|
||||
<span class="events-nearby title">Events nearby you</span>
|
||||
<section class="container">
|
||||
<h3 class="events-nearby title"><translate>Events nearby you</translate></h3>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-if="events.length > 0" class="columns is-multiline">
|
||||
<EventCard
|
||||
v-for="event in events"
|
||||
:key="event.uuid"
|
||||
:event="event"
|
||||
class="column is-one-quarter-desktop is-half-mobile"
|
||||
/>
|
||||
<div class="column is-one-third-desktop" v-for="event in events.slice(0, 6)" :key="event.uuid">
|
||||
<EventCard
|
||||
:event="event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<b-message v-else type="is-danger">
|
||||
<translate>No events found</translate>
|
||||
@@ -91,7 +93,9 @@ import { ICurrentUser } from '@/types/current-user.model';
|
||||
import { CURRENT_USER_CLIENT } from '@/graphql/user';
|
||||
import { RouteName } from '@/router';
|
||||
import { IEvent } from '@/types/event.model';
|
||||
import DateComponent from '@/components/Event/Date.vue';
|
||||
import DateComponent from '@/components/Event/DateCalendarIcon.vue';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
@@ -105,6 +109,9 @@ import DateComponent from '@/components/Event/Date.vue';
|
||||
currentUser: {
|
||||
query: CURRENT_USER_CLIENT,
|
||||
},
|
||||
config: {
|
||||
query: CONFIG,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
DateComponent,
|
||||
@@ -112,18 +119,19 @@ import DateComponent from '@/components/Event/Date.vue';
|
||||
},
|
||||
})
|
||||
export default class Home extends Vue {
|
||||
events = [];
|
||||
events: Event[] = [];
|
||||
locations = [];
|
||||
city = { name: null };
|
||||
country = { name: null };
|
||||
loggedPerson: IPerson = new Person();
|
||||
currentUser!: ICurrentUser;
|
||||
config: IConfig = { description: '', name: '', registrationsOpen: false };
|
||||
|
||||
get displayed_name() {
|
||||
return this.loggedPerson.name === null
|
||||
? this.loggedPerson.preferredUsername
|
||||
: this.loggedPerson.name;
|
||||
}
|
||||
// get displayed_name() {
|
||||
// return this.loggedPerson && this.loggedPerson.name === null
|
||||
// ? this.loggedPerson.preferredUsername
|
||||
// : this.loggedPerson.name;
|
||||
// }
|
||||
|
||||
isToday(date: string) {
|
||||
return (new Date(date)).toDateString() === (new Date()).toDateString();
|
||||
@@ -153,19 +161,18 @@ export default class Home extends Vue {
|
||||
|
||||
get goingToEvents(): Map<string, IEvent[]> {
|
||||
const res = this.$data.loggedPerson.goingToEvents.filter((event) => {
|
||||
return event.beginsOn != null && this.isBefore(event.beginsOn, 0)
|
||||
return event.beginsOn != null && this.isBefore(event.beginsOn, 0);
|
||||
});
|
||||
res.sort(
|
||||
(a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn),
|
||||
);
|
||||
const groups = res.reduce((acc: Map<string, IEvent[]>, event: IEvent) => {
|
||||
return res.reduce((acc: Map<string, IEvent[]>, event: IEvent) => {
|
||||
const day = (new Date(event.beginsOn)).toDateString();
|
||||
const events: IEvent[] = acc.get(day) || [];
|
||||
events.push(event);
|
||||
acc.set(day, events);
|
||||
return acc;
|
||||
}, new Map());
|
||||
return groups;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
geoLocalize() {
|
||||
@@ -210,9 +217,9 @@ export default class Home extends Vue {
|
||||
this.$router.push({ name: RouteName.EVENT, params: { uuid: event.uuid } });
|
||||
}
|
||||
|
||||
ipLocation() {
|
||||
return this.city.name ? this.city.name : this.country.name;
|
||||
}
|
||||
// ipLocation() {
|
||||
// return this.city.name ? this.city.name : this.country.name;
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,63 @@
|
||||
<template>
|
||||
<section>
|
||||
<h1>
|
||||
<translate>Page not found!</translate>
|
||||
<img src="../assets/oh_no.jpg">
|
||||
</h1>
|
||||
<section class="container has-text-centered not-found">
|
||||
<div class="columns is-vertical">
|
||||
<div class="column is-centered">
|
||||
<img src="../assets/oh_no.jpg" alt="Not found 'oh no' picture">
|
||||
<h1 class="title">
|
||||
<translate>The page you're looking for doesn't exist.</translate>
|
||||
</h1>
|
||||
<p>
|
||||
<translate>Please make sure the address is correct and that the page hasn't been moved.</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Please contact this instance's Mobilizon admin if you think this is a mistake.</translate>
|
||||
</p>
|
||||
<!-- The following should just be replaced with the SearchField component but it fails for some reason -->
|
||||
<form @submit="enter">
|
||||
<b-field class="search">
|
||||
<b-input expanded icon="magnify" type="search" :placeholder="searchPlaceHolder" v-model="searchText" />
|
||||
<p class="control">
|
||||
<button type="submit" class="button is-primary"><translate>Search</translate></button>
|
||||
</p>
|
||||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { RouteName } from '@/router';
|
||||
import BField from 'buefy/src/components/field/Field.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
BField,
|
||||
},
|
||||
})
|
||||
export default class PageNotFound extends Vue {
|
||||
searchText: string = '';
|
||||
|
||||
get searchPlaceHolder(): string {
|
||||
return this.$gettext('Search events, groups, etc.');
|
||||
}
|
||||
|
||||
enter() {
|
||||
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchText } });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.container.not-found {
|
||||
margin: auto;
|
||||
max-width: 600px;
|
||||
|
||||
img {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
140
js/src/views/Search.vue
Normal file
140
js/src/views/Search.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<section class="container">
|
||||
<h1>
|
||||
<translate :translate-params="{ search: this.searchTerm }">Search results: « %{ search } »</translate>
|
||||
</h1>
|
||||
<b-loading :active.sync="$apollo.loading" />
|
||||
<b-tabs v-model="activeTab" type="is-boxed" class="searchTabs" @change="changeTab">
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="calendar"></b-icon>
|
||||
<span><translate>Events</translate> <b-tag rounded>{{ events.length }}</b-tag> </span>
|
||||
</template>
|
||||
<div v-if="search.length > 0" class="columns is-multiline">
|
||||
<div class="column is-one-quarter-desktop is-half-mobile"
|
||||
v-for="event in events"
|
||||
:key="event.uuid">
|
||||
<EventCard
|
||||
:event="event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<b-message v-else-if="$apollo.loading === false" type="is-danger">
|
||||
<translate>No events found</translate>
|
||||
</b-message>
|
||||
</b-tab-item>
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="account-multiple"></b-icon>
|
||||
<span><translate>Groups</translate> <b-tag rounded>{{ groups.length }}</b-tag> </span>
|
||||
</template>
|
||||
<div v-if="groups.length > 0" class="columns is-multiline">
|
||||
<div class="column is-one-quarter-desktop is-half-mobile"
|
||||
v-for="group in groups"
|
||||
:key="group.uuid">
|
||||
<group-card :group="group" />
|
||||
</div>
|
||||
</div>
|
||||
<b-message v-else-if="$apollo.loading === false" type="is-danger">
|
||||
<translate>No groups found</translate>
|
||||
</b-message>
|
||||
</b-tab-item>
|
||||
</b-tabs>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { SEARCH } from '@/graphql/search';
|
||||
import { RouteName } from '@/router';
|
||||
import { IEvent } from '@/types/event.model';
|
||||
import { ISearch } from '@/types/search.model';
|
||||
import EventCard from '@/components/Event/EventCard.vue';
|
||||
import { IGroup, Group } from '@/types/actor.model';
|
||||
import GroupCard from '@/components/Group/GroupCard.vue';
|
||||
|
||||
enum SearchTabs {
|
||||
EVENTS = 0,
|
||||
GROUPS = 1,
|
||||
PERSONS = 2, // not used right now
|
||||
}
|
||||
|
||||
const tabsName = {
|
||||
events: SearchTabs.EVENTS,
|
||||
groups: SearchTabs.GROUPS,
|
||||
};
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
search: {
|
||||
query: SEARCH,
|
||||
variables() {
|
||||
return {
|
||||
searchText: this.searchTerm,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.searchTerm;
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
GroupCard,
|
||||
EventCard,
|
||||
},
|
||||
})
|
||||
export default class Search extends Vue {
|
||||
@Prop({ type: String, required: true }) searchTerm!: string;
|
||||
@Prop({ type: String, required: false, default: 'events' }) searchType!: string;
|
||||
|
||||
search = [];
|
||||
activeTab: SearchTabs = tabsName[this.searchType];
|
||||
|
||||
changeTab(index: number) {
|
||||
switch (index) {
|
||||
case SearchTabs.EVENTS:
|
||||
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchTerm, searchType: 'events' } });
|
||||
break;
|
||||
case SearchTabs.GROUPS:
|
||||
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchTerm, searchType: 'groups' } });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('search')
|
||||
changeTabForResult() {
|
||||
if (this.events.length === 0 && this.groups.length > 0) {
|
||||
this.activeTab = SearchTabs.GROUPS;
|
||||
}
|
||||
if (this.groups.length === 0 && this.events.length > 0) {
|
||||
this.activeTab = SearchTabs.EVENTS;
|
||||
}
|
||||
}
|
||||
|
||||
@Watch('search')
|
||||
@Watch('$route')
|
||||
async loadSearch() {
|
||||
await this.$apollo.queries['search'].refetch();
|
||||
}
|
||||
|
||||
get events(): IEvent[] {
|
||||
return this.search.filter((value: ISearch) => { return value.__typename === 'Event'; }) as IEvent[];
|
||||
}
|
||||
|
||||
get groups(): IGroup[] {
|
||||
const groups = this.search.filter((value: ISearch) => { return value.__typename === 'Group'; }) as IGroup[];
|
||||
return groups.map(group => Object.assign(new Group(), group));
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
@import "~bulma/sass/components/tabs";
|
||||
@import "~buefy/src/scss/components/tabs";
|
||||
@import "~bulma/sass/elements/tag";
|
||||
|
||||
.searchTabs .tab-content {
|
||||
background: #fff;
|
||||
min-height: 10em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container">
|
||||
<section class="hero">
|
||||
<h1 class="title">
|
||||
<translate>Welcome back!</translate>
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
<section v-if="!currentUser.isLoggedIn">
|
||||
<div class="columns is-mobile is-centered">
|
||||
<div class="column is-half card">
|
||||
<div class="column is-half">
|
||||
<b-message title="Error" type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message>
|
||||
<form @submit="loginAction">
|
||||
<b-field label="Email">
|
||||
<b-field :label="$gettext('Email')">
|
||||
<b-input aria-required="true" required type="email" v-model="credentials.email"/>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Password">
|
||||
<b-field :label="$gettext('Password')">
|
||||
<b-input
|
||||
aria-required="true"
|
||||
required
|
||||
@@ -70,20 +70,20 @@ import { ILogin } from '@/types/login.model';
|
||||
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
|
||||
import { onLogin } from '@/vue-apollo';
|
||||
import { RouteName } from '@/router';
|
||||
import { LoginErrorCode } from '@/types/login-error-code.model'
|
||||
import { ICurrentUser } from '@/types/current-user.model'
|
||||
import { CONFIG } from '@/graphql/config'
|
||||
import { IConfig } from '@/types/config.model'
|
||||
import { LoginErrorCode } from '@/types/login-error-code.model';
|
||||
import { ICurrentUser } from '@/types/current-user.model';
|
||||
import { CONFIG } from '@/graphql/config';
|
||||
import { IConfig } from '@/types/config.model';
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
config: {
|
||||
query: CONFIG
|
||||
query: CONFIG,
|
||||
},
|
||||
currentUser: {
|
||||
query: CURRENT_USER_CLIENT
|
||||
}
|
||||
}
|
||||
query: CURRENT_USER_CLIENT,
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Login extends Vue {
|
||||
@Prop({ type: String, required: false, default: '' }) email!: string;
|
||||
@@ -113,9 +113,9 @@ export default class Login extends Vue {
|
||||
this.credentials.email = this.email;
|
||||
this.credentials.password = this.password;
|
||||
|
||||
let query = this.$route.query;
|
||||
this.errorCode = query[ 'code' ] as LoginErrorCode;
|
||||
this.redirect = query[ 'redirect' ] as string;
|
||||
const query = this.$route.query;
|
||||
this.errorCode = query['code'] as LoginErrorCode;
|
||||
this.redirect = query['redirect'] as string;
|
||||
}
|
||||
|
||||
async loginAction(e: Event) {
|
||||
@@ -146,7 +146,7 @@ export default class Login extends Vue {
|
||||
onLogin(this.$apollo);
|
||||
|
||||
if (this.redirect) {
|
||||
this.$router.push(this.redirect)
|
||||
this.$router.push(this.redirect);
|
||||
} else {
|
||||
this.$router.push({ name: RouteName.HOME });
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="columns is-mobile">
|
||||
<div class="column">
|
||||
<div class="content">
|
||||
<h2 class="subtitle" v-translate>Features</h2>
|
||||
<h3 class="title" v-translate>Features</h3>
|
||||
<ul>
|
||||
<li v-translate>Create your communities and your events</li>
|
||||
<li v-translate>Other stuff…</li>
|
||||
@@ -24,7 +24,7 @@
|
||||
</p>
|
||||
<hr>
|
||||
<div class="content">
|
||||
<h2 class="subtitle" v-translate>About this instance</h2>
|
||||
<h3 class="title" v-translate>About this instance</h3>
|
||||
<p>
|
||||
<translate>Your local administrator resumed it's policy:</translate>
|
||||
</p>
|
||||
@@ -96,9 +96,7 @@
|
||||
</form>
|
||||
|
||||
<div v-if="errors.length > 0">
|
||||
<b-message type="is-danger" v-for="error in errors" :key="error">
|
||||
<translate>{{ error }}</translate>
|
||||
</b-message>
|
||||
<b-message type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,6 +152,8 @@ export default class Register extends Vue {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../variables";
|
||||
|
||||
.avatar-enter-active {
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
@@ -166,4 +166,9 @@ export default class Register extends Vue {
|
||||
.avatar-leave {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h3.title {
|
||||
background: $secondary;
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<section class="columns">
|
||||
<div class="column card">
|
||||
<section class="container">
|
||||
<div class="column">
|
||||
<h1 class="title">
|
||||
<translate>Resend confirmation email</translate>
|
||||
</h1>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<section class="columns">
|
||||
<div class="card column">
|
||||
<section class="container">
|
||||
<div class="column">
|
||||
<h1 class="title">
|
||||
<translate>Password reset</translate>
|
||||
</h1>
|
||||
|
||||
Reference in New Issue
Block a user