Mostly UI stuff

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2018-07-09 14:19:24 +02:00
parent b60e8b7647
commit e170aa7f66
16 changed files with 450 additions and 296 deletions

2
.gitignore vendored
View file

@ -17,3 +17,5 @@ erl_crash.dump
.elixir_ls .elixir_ls
/doc /doc
priv/static/GeoLite2-City.mmdb
.vscode/

View file

@ -48,3 +48,12 @@ config :guardian, Guardian.DB,
schema_name: "guardian_tokens", # default schema_name: "guardian_tokens", # default
token_types: ["refresh_token"], # store all token types if not set token_types: ["refresh_token"], # store all token types if not set
sweep_interval: 60 # default: 60 minutes sweep_interval: 60 # default: 60 minutes
config :geolix,
databases: [
%{
id: :city,
adapter: Geolix.Adapter.MMDB2,
source: "priv/static/GeoLite2-City.mmdb"
}
]

View file

@ -9,6 +9,22 @@
enable-resize-watcher enable-resize-watcher
> >
<v-list dense> <v-list dense>
<v-list-tile avatar v-if="$store.state.user">
<v-list-tile-avatar>
<img v-if="!getUser().actor.avatar_url"
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="getUser().actor.avatar_url"
>
</v-list-tile-avatar>
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: getUser().actor.username }})">
<v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<template v-for="(item, i) in items" v-if="showMenuItem(item.role)"> <template v-for="(item, i) in items" v-if="showMenuItem(item.role)">
<v-layout <v-layout
row row
@ -40,8 +56,8 @@
</v-navigation-drawer> </v-navigation-drawer>
<NavBar v-bind="{toggleDrawer}"></NavBar> <NavBar v-bind="{toggleDrawer}"></NavBar>
<v-content> <v-content>
<v-container fluid fill-height> <v-container fluid fill-height :class="{'px-0': $vuetify.breakpoint.xsOnly }">
<v-layout xs-12> <v-layout xs12>
<transition name="router"> <transition name="router">
<router-view></router-view> <router-view></router-view>
</transition> </transition>
@ -140,8 +156,13 @@ export default {
}, },
toggleDrawer() { toggleDrawer() {
this.drawer = !this.drawer; this.drawer = !this.drawer;
} },
}, },
computed: {
displayed_name() {
return this.$store.state.user.actor.display_name === null ? this.$store.state.user.actor.username : this.$store.state.user.actor.display_name
},
}
}; };
</script> </script>

View file

@ -1,177 +1,175 @@
<template> <template>
<v-container> <v-layout row>
<v-layout row> <v-flex xs12 sm6 offset-sm3>
<v-flex xs12 sm6 offset-sm3> <v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular> <v-card v-if="!loading">
<v-card v-if="!loading"> <v-card-media :src="actor.banner" height="400px">
<v-card-media :src="actor.banner" height="400px"> <v-layout column class="media">
<v-layout column class="media"> <v-card-title>
<v-card-title> <v-btn icon @click="$router.go(-1)">
<v-btn icon @click="$router.go(-1)"> <v-icon>chevron_left</v-icon>
<v-icon>chevron_left</v-icon> </v-btn>
</v-btn>
<v-spacer></v-spacer>
<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
<v-icon>edit</v-icon>
</v-btn>
<v-menu bottom left>
<v-btn icon slot="activator">
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<v-list-tile @click="logoutUser()" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
<v-list-tile-title>User logout</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="deleteAccount()" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
<v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-card-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<div class="text-xs-center"> <v-btn icon class="mr-3" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
<v-avatar size="125px"> <v-icon>edit</v-icon>
<img v-if="!actor.avatar_url" </v-btn>
class="img-circle elevation-7 mb-1" <v-menu bottom left>
src="https://picsum.photos/125/125/" <v-btn icon slot="activator">
> <v-icon>more_vert</v-icon>
<img v-else </v-btn>
class="img-circle elevation-7 mb-1" <v-list>
:src="actor.avatar_url" <v-list-tile @click="logoutUser()" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
> <v-list-tile-title>User logout</v-list-tile-title>
</v-avatar> </v-list-tile>
</div> <v-list-tile @click="deleteAccount()" v-if="$store.state.user && $store.state.user.actor.id === actor.id">
<v-container fluid grid-list-lg> <v-list-tile-title>Delete</v-list-tile-title>
<v-layout row> </v-list-tile>
<v-flex xs7> </v-list>
<div class="headline">{{ actor.display_name }}</div> </v-menu>
<div><span class="subheading">@{{ actor.username }}<span v-if="actor.domain">@{{ actor.domain }}</span></span></div> </v-card-title>
<v-card-text v-if="actor.description" v-html="actor.description"></v-card-text> <v-spacer></v-spacer>
</v-flex> <div class="text-xs-center">
</v-layout> <v-avatar size="125px">
</v-container> <img v-if="!actor.avatar_url"
</v-layout> class="img-circle elevation-7 mb-1"
</v-card-media> src="https://picsum.photos/125/125/"
<v-list three-line> >
<v-list-tile> <img v-else
<v-list-tile-action> class="img-circle elevation-7 mb-1"
<v-icon color="indigo">phone</v-icon> :src="actor.avatar_url"
</v-list-tile-action> >
<v-list-tile-content> </v-avatar>
<v-list-tile-title>(323) 555-6789</v-list-tile-title> </div>
<v-list-tile-sub-title>Work</v-list-tile-sub-title> <v-container fluid grid-list-lg>
</v-list-tile-content> <v-layout row>
<v-list-tile-action> <v-flex xs7>
<v-icon dark>chat</v-icon> <div class="headline">{{ actor.display_name }}</div>
</v-list-tile-action> <div><span class="subheading">@{{ actor.username }}<span v-if="actor.domain">@{{ actor.domain }}</span></span></div>
</v-list-tile> <v-card-text v-if="actor.description" v-html="actor.description"></v-card-text>
<v-divider inset></v-divider> </v-flex>
<v-list-tile> </v-layout>
<v-list-tile-action> </v-container>
<v-icon color="indigo">mail</v-icon> </v-layout>
</v-list-tile-action> </v-card-media>
<v-list-tile-content> <v-list three-line>
<v-list-tile-title>ali_connors@example.com</v-list-tile-title> <v-list-tile>
<v-list-tile-sub-title>Work</v-list-tile-sub-title> <v-list-tile-action>
</v-list-tile-content> <v-icon color="indigo">phone</v-icon>
</v-list-tile> </v-list-tile-action>
<v-divider inset></v-divider> <v-list-tile-content>
<v-list-tile> <v-list-tile-title>(323) 555-6789</v-list-tile-title>
<v-list-tile-action> <v-list-tile-sub-title>Work</v-list-tile-sub-title>
<v-icon color="indigo">location_on</v-icon> </v-list-tile-content>
</v-list-tile-action> <v-list-tile-action>
<v-list-tile-content> <v-icon dark>chat</v-icon>
<v-list-tile-title>1400 Main Street</v-list-tile-title> </v-list-tile-action>
<v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title> </v-list-tile>
</v-list-tile-content> <v-divider inset></v-divider>
</v-list-tile> <v-list-tile>
</v-list> <v-list-tile-action>
<v-container fluid grid-list-md v-if="actor.participatingEvents && actor.participatingEvents.length > 0"> <v-icon color="indigo">mail</v-icon>
<v-subheader>Participated at</v-subheader> </v-list-tile-action>
<v-layout row wrap> <v-list-tile-content>
<v-flex v-for="event in actor.participatingEvents" :key="event.id"> <v-list-tile-title>ali_connors@example.com</v-list-tile-title>
<v-card> <v-list-tile-sub-title>Work</v-list-tile-sub-title>
<v-card-media </v-list-tile-content>
class="black--text" </v-list-tile>
height="200px" <v-divider inset></v-divider>
src="https://picsum.photos/400/200/" <v-list-tile>
> <v-list-tile-action>
<v-container fill-height fluid> <v-icon color="indigo">location_on</v-icon>
<v-layout fill-height> </v-list-tile-action>
<v-flex xs12 align-end flexbox> <v-list-tile-content>
<span class="headline">{{ event.title }}</span> <v-list-tile-title>1400 Main Street</v-list-tile-title>
</v-flex> <v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title>
</v-layout> </v-list-tile-content>
</v-container> </v-list-tile>
</v-card-media> </v-list>
<v-card-title> <v-container fluid grid-list-md v-if="actor.participatingEvents && actor.participatingEvents.length > 0">
<div> <v-subheader>Participated at</v-subheader>
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br> <v-layout row wrap>
<p>{{ event.description }}</p> <v-flex v-for="event in actor.participatingEvents" :key="event.id">
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p> <v-card>
</div> <v-card-media
</v-card-title> class="black--text"
<v-card-actions> height="200px"
<v-spacer></v-spacer> src="https://picsum.photos/400/200/"
<v-btn icon> >
<v-icon>favorite</v-icon> <v-container fill-height fluid>
</v-btn> <v-layout fill-height>
<v-btn icon> <v-flex xs12 align-end flexbox>
<v-icon>bookmark</v-icon> <span class="headline">{{ event.title }}</span>
</v-btn> </v-flex>
<v-btn icon> </v-layout>
<v-icon>share</v-icon> </v-container>
</v-btn> </v-card-media>
</v-card-actions> <v-card-title>
</v-card> <div>
</v-flex> <span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
</v-layout> <p>{{ event.description }}</p>
</v-container> <p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
<v-container fluid grid-list-md v-if="actor.organizingEvents && actor.organizingEvents.length > 0"> </div>
<v-subheader>Organized events</v-subheader> </v-card-title>
<v-layout row wrap> <v-card-actions>
<v-flex v-for="event in actor.organizingEvents" :key="event.id"> <v-spacer></v-spacer>
<v-card> <v-btn icon>
<v-card-media <v-icon>favorite</v-icon>
class="black--text" </v-btn>
height="200px" <v-btn icon>
src="https://picsum.photos/400/200/" <v-icon>bookmark</v-icon>
> </v-btn>
<v-container fill-height fluid> <v-btn icon>
<v-layout fill-height> <v-icon>share</v-icon>
<v-flex xs12 align-end flexbox> </v-btn>
<span class="headline">{{ event.title }}</span> </v-card-actions>
</v-flex> </v-card>
</v-layout> </v-flex>
</v-container> </v-layout>
</v-card-media> </v-container>
<v-card-title> <v-container fluid grid-list-md v-if="actor.organizingEvents && actor.organizingEvents.length > 0">
<div> <v-subheader>Organized events</v-subheader>
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br> <v-layout row wrap>
<p>{{ event.description }}</p> <v-flex v-for="event in actor.organizingEvents" :key="event.id">
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p> <v-card>
</div> <v-card-media
</v-card-title> class="black--text"
<v-card-actions> height="200px"
<v-spacer></v-spacer> src="https://picsum.photos/400/200/"
<v-btn icon> >
<v-icon>favorite</v-icon> <v-container fill-height fluid>
</v-btn> <v-layout fill-height>
<v-btn icon> <v-flex xs12 align-end flexbox>
<v-icon>bookmark</v-icon> <span class="headline">{{ event.title }}</span>
</v-btn> </v-flex>
<v-btn icon> </v-layout>
<v-icon>share</v-icon> </v-container>
</v-btn> </v-card-media>
</v-card-actions> <v-card-title>
</v-card> <div>
</v-flex> <span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
</v-layout> <p>{{ event.description }}</p>
</v-container> <p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
</v-card> </div>
</v-flex> </v-card-title>
</v-layout> <v-card-actions>
</v-container> <v-spacer></v-spacer>
<v-btn icon>
<v-icon>favorite</v-icon>
</v-btn>
<v-btn icon>
<v-icon>bookmark</v-icon>
</v-btn>
<v-btn icon>
<v-icon>share</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
</v-layout>
</template> </template>
<script> <script>

View file

@ -129,7 +129,7 @@
.then(response => response.json()) .then(response => response.json())
.then((data) => { .then((data) => {
this.loading = false; this.loading = false;
this.$router.push({name: 'Event', params: {uuid: data.uuid}}); this.$router.push({name: 'Event', params: {uuid: data.data.uuid}});
}).catch((err) => { }).catch((err) => {
Promise.resolve(err).then((err) => { Promise.resolve(err).then((err) => {
console.log('err creation', err); console.log('err creation', err);

View file

@ -1,95 +1,140 @@
<template> <template>
<v-container> <v-layout row>
<v-layout row> <v-flex xs12 sm6 offset-sm3>
<v-flex xs12 sm6 offset-sm3> <span v-if="error">Error : event not found</span>
<span v-if="error">Error : event not found</span> <v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular> <v-card v-if="!loading && !error">
<v-card v-if="!loading && !error"> <v-card-media
<v-layout column class="media"> src="https://picsum.photos/600/400/"
<v-card-title> height="200px"
<v-btn icon @click="$router.go(-1)"> >
<v-icon>chevron_left</v-icon> <v-container fill-height fluid>
</v-btn> <v-layout fill-height>
<v-flex xs12 align-end flexbox>
<v-card-title>
<v-btn icon @click="$router.go(-1)" class="white--text">
<v-icon>chevron_left</v-icon>
</v-btn>
<v-spacer></v-spacer>
<v-btn icon class="mr-3 white--text" v-if="event.organizer.id === $store.state.user.actor.id" :to="{ name: 'EditEvent', params: {id: event.id}}">
<v-icon>edit</v-icon>
</v-btn>
<v-menu bottom left>
<v-btn icon slot="activator" class="white--text">
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<v-list-tile @click="downloadIcsEvent()">
<v-list-tile-title>Download</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="deleteEvent()" v-if="$store.state.user.actor.id === event.organizer.id">
<v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</v-card-title>
</v-flex>
</v-layout>
</v-container>
</v-card-media>
<v-container grid-list-md>
<v-layout row wrap>
<v-flex md10>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon class="mr-3" v-if="event.organizer.id === $store.state.user.actor.id" :to="{ name: 'EditEvent', params: {id: event.id}}"> <span class="subheading grey--text">{{ event.begins_on | formatDay }}</span>
<v-icon>edit</v-icon> <h1 class="display-1">{{ event.title }}</h1>
</v-btn> <div>
<v-menu bottom left> <router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
<v-btn icon slot="activator"> <v-avatar size="25px">
<v-icon>more_vert</v-icon> <img class="img-circle elevation-7 mb-1"
</v-btn> :src="event.organizer.avatar"
<v-list> >
<v-list-tile @click="downloadIcsEvent()"> </v-avatar>
<v-list-tile-title>Download</v-list-tile-title> </router-link>
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name ? event.organizer.display_name : event.organizer.username }}</span>
</div>
<!--<p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey&#45;&#45;text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p>
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>-->
</v-flex>
<v-flex md2>
<p v-if="!actorIsParticipant()">Vous y allez ?
<span class="text--darken-2 grey--text">{{ event.participants.length }} personnes y vont.</span>
</p>
<p v-else>
Vous avez annoncé aller à cet événement.
</p>
<v-card-actions>
<v-btn v-if="!actorIsParticipant()" @click="joinEvent" color="success"><v-icon>check</v-icon> Join</v-btn>
<v-btn v-if="actorIsParticipant()" @click="leaveEvent" color="error">Leave</v-btn>
</v-card-actions>
</v-flex>
</v-layout>
</v-container>
<v-divider></v-divider>
<v-container>
<v-layout row wrap>
<v-flex xs12 md4 order-md1>
<v-layout
column
fill-height
>
<v-list two-line>
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">access_time</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ event.begins_on | formatDate }}</v-list-tile-title>
<v-list-tile-sub-title>{{ event.ends_on | formatDate }}</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile> </v-list-tile>
<v-list-tile @click="deleteEvent()" v-if="$store.state.user.actor.id === event.organizer.id">
<v-list-tile-title>Delete</v-list-tile-title> <v-divider inset></v-divider>
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">place</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title><span v-if="event.address_type === 'physical'">
{{ event.physical_address.streetAddress }}
</span></v-list-tile-title>
<v-list-tile-sub-title>Mobile</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile> </v-list-tile>
</v-list> </v-list>
</v-menu> </v-layout>
</v-card-title> </v-flex>
<v-container grid-list-md text-xs-center> <v-flex md8 xs12>
<v-card-media <p>
src="https://picsum.photos/600/400/" <h2>Details</h2>
height="200px" <vue-markdown :source="event.description" v-if="event.description" :toc-first-level="3" />
> </p>
<v-subheader>Participants</v-subheader>
</v-card-media> <v-flex md2 v-for="actor in event.participants" :key="actor.uuid">
<v-layout row wrap>
<v-flex xs6>
<v-spacer></v-spacer>
<span class="subheading grey--text">{{ event.begins_on | formatDay }}</span>
<h1 class="display-2">{{ event.title }}</h1>
<div>
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
<v-avatar size="25px">
<img class="img-circle elevation-7 mb-1"
:src="event.organizer.avatar"
>
</v-avatar>
</router-link>
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name ? event.organizer.display_name : event.organizer.username }}</span>
</div>
<p>
<vue-markdown :source="event.description" v-if="event.description" />
</p>
<!--<p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey&#45;&#45;text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p>
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>-->
</v-flex>
<v-flex xs6>
<v-card-actions>
<v-btn color="success" v-if="!event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="joinEvent" class="btn btn-primary"><v-icon>check</v-icon> Join</v-btn>
<v-btn v-if="event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="leaveEvent" class="btn btn-primary">Leave</v-btn>
</v-card-actions>
</v-flex>
</v-layout>
</v-container>
<v-container fluid grid-list-md>
<v-subheader>Membres</v-subheader>
<v-layout row>
<v-flex xs2 v-for="actor in event.participants" :key="actor.uuid">
<router-link :to="{name: 'Account', params: { name: actor.username }}"> <router-link :to="{name: 'Account', params: { name: actor.username }}">
<v-avatar size="75px"> <v-avatar size="75px">
<img v-if="!actor.avatar" <img v-if="!actor.avatar"
class="img-circle elevation-7 mb-1" class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/" src="https://picsum.photos/125/125/"
> >
<img v-else <img v-else
class="img-circle elevation-7 mb-1" class="img-circle elevation-7 mb-1"
:src="actor.avatar" :src="actor.avatar"
> >
</v-avatar> </v-avatar>
</router-link> </router-link>
<span>{{ actor.username }}</span> <span>{{ actor.username }}</span>
</v-flex> </v-flex>
<span v-if="event.participants.length === 0">No participants yet.</span> </v-flex>
</v-layout> <span v-if="event.participants.length === 0">No participants yet.</span>
</v-container>
</v-layout> </v-layout>
</v-card> </v-container>
</v-flex> </v-card>
</v-layout> </v-flex>
</v-container> </v-layout>
</template> </template>
<script> <script>
@ -136,11 +181,11 @@
console.log(data); console.log(data);
this.error = true; this.error = true;
this.loading = false; this.loading = false;
}) });
}); });
}, },
joinEvent() { joinEvent() {
eventFetch(`/events/${this.uuid}/join`, this.$store) eventFetch(`/events/${this.uuid}/join`, this.$store, { method: 'POST' })
.then(response => response.json()) .then(response => response.json())
.then((data) => { .then((data) => {
console.log(data); console.log(data);
@ -166,6 +211,10 @@
document.body.removeChild(link); document.body.removeChild(link);
}) })
}, },
actorIsParticipant() {
const actorId = this.$store.state.user.actor.id;
return this.event.participants.map(participant => participant.id).includes(actorId) || actorId === this.event.organizer.id;
}
}, },
props: { props: {
uuid: { uuid: {
@ -180,6 +229,8 @@
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> <style>
.v-card__media__background {
filter: contrast(0.4);
}
</style> </style>

View file

@ -1,10 +1,10 @@
<template> <template>
<v-container> <v-container>
<v-jumbotron <v-jumbotron
:gradient="gradient" :gradient="gradient"
src="https://picsum.photos/1200/900" src="https://picsum.photos/1200/900"
dark dark
v-if="$store.state.user === false" v-if="$store.state.user === false"
> >
<v-container fill-height> <v-container fill-height>
<v-layout align-center> <v-layout align-center>
@ -18,9 +18,23 @@
</v-jumbotron> </v-jumbotron>
<v-layout> <v-layout>
<v-flex xs12 sm8 offset-sm2> <v-flex xs12 sm8 offset-sm2>
<v-card> <v-layout row wrap>
<v-flex xs12 sm6>
<h1>Welcome back {{ $store.state.user.actor.username }}</h1>
</v-flex>
<v-flex xs12 sm6>
<v-layout align-center>
<span class="events-nearby title">Events nearby </span><v-text-field
solo
append-icon="place"
:value="ipLocation()"
></v-text-field>
</v-layout>
</v-flex>
</v-layout>
<v-card v-if="events.length > 0">
<v-layout row wrap> <v-layout row wrap>
<v-flex xs4 v-for="event in events" :key="event.uuid"> <v-flex md4 v-for="event in events" :key="event.uuid">
<v-card :to="{ name: 'Event', params:{ uuid: event.uuid } }"> <v-card :to="{ name: 'Event', params:{ uuid: event.uuid } }">
<v-card-media v-if="!event.image" <v-card-media v-if="!event.image"
class="white--text" class="white--text"
@ -45,43 +59,27 @@
> >
</v-avatar> </v-avatar>
</router-link> </router-link>
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name }}</span> <span v-if="event.organizer">Organisé par {{ event.organizer.display_name ? event.organizer.display_name : event.organizer.username }}</span>
</div> </div>
</v-card-title> </v-card-title>
</v-card> </v-card>
</v-flex> </v-flex>
</v-layout> </v-layout>
</v-card> </v-card>
<v-alert v-else :value="true" type="info">
No events found nearby {{ ipLocation() }}
</v-alert>
</v-flex> </v-flex>
</v-layout> </v-layout>
<v-layout row>
<v-flex xs6>
<v-btn large @click="geoLocalize"><v-icon>my_location</v-icon>Me géolocaliser</v-btn>
</v-flex>
<v-flex xs6>
<vuetify-google-autocomplete
id="map"
append-icon="search"
classname="form-control"
placeholder="Start typing"
enable-geolocation
types="(cities)"
v-on:placechanged="getAddressData"
>
</vuetify-google-autocomplete>
</v-flex>
</v-layout>
</v-container> </v-container>
</template> </template>
<script> <script>
import VuetifyGoogleAutocomplete from 'vuetify-google-autocomplete';
import ngeohash from 'ngeohash'; import ngeohash from 'ngeohash';
import eventFetch from "../api/eventFetch"; import eventFetch from "../api/eventFetch";
export default { export default {
components: { VuetifyGoogleAutocomplete },
name: 'Home', name: 'Home',
data() { data() {
return { return {
@ -94,6 +92,8 @@ export default {
}, },
locations: [], locations: [],
events: [], events: [],
city: {name: null},
country: {name: null},
}; };
}, },
created() { created() {
@ -118,6 +118,8 @@ export default {
.then((response) => { .then((response) => {
this.loading = false; this.loading = false;
this.events = response.data; this.events = response.data;
this.city = response.city;
this.country = response.country;
}); });
}, },
geoLocalize() { geoLocalize() {
@ -147,6 +149,9 @@ export default {
viewEvent(event) { viewEvent(event) {
this.$router.push({ name: 'Event', params: { uuid: event.uuid } }) this.$router.push({ name: 'Event', params: { uuid: event.uuid } })
}, },
ipLocation() {
return this.city.name ? this.city.name : this.country.name;
}
}, },
}; };
</script> </script>
@ -157,4 +162,8 @@ export default {
border: 1px solid #dbdbdb; border: 1px solid #dbdbdb;
color: rgba(0,0,0,.87); color: rgba(0,0,0,.87);
} }
.events-nearby {
margin-bottom: 25px;
}
</style> </style>

View file

@ -23,6 +23,7 @@
:items="searchElement.items" :items="searchElement.items"
:search-input.sync="search" :search-input.sync="search"
v-model="searchSelect" v-model="searchSelect"
return-object
> >
<template slot="item" slot-scope="data"> <template slot="item" slot-scope="data">
<template v-if="typeof data.item !== 'object'"> <template v-if="typeof data.item !== 'object'">
@ -30,7 +31,8 @@
</template> </template>
<template v-else> <template v-else>
<v-list-tile-avatar> <v-list-tile-avatar>
<img :src="data.item.avatar"> <img :src="data.item.avatar" v-if="data.item.avatar">
<v-icon v-else>event</v-icon>
</v-list-tile-avatar> </v-list-tile-avatar>
<v-list-tile-content> <v-list-tile-content>
<v-list-tile-title v-html="username_with_domain(data.item)"></v-list-tile-title> <v-list-tile-title v-html="username_with_domain(data.item)"></v-list-tile-title>
@ -45,6 +47,7 @@
:close-on-content-click="false" :close-on-content-click="false"
:nudge-width="200" :nudge-width="200"
v-model="notificationMenu" v-model="notificationMenu"
v-if="getUser()"
> >
<v-btn icon slot="activator"> <v-btn icon slot="activator">
<v-badge left color="red"> <v-badge left color="red">
@ -73,6 +76,7 @@
</v-card> </v-card>
</v-menu> </v-menu>
<v-btn flat @click="$router.push({name: 'Account', params: { name: getUser().actor.username }})" v-if="$store.state.user">{{ this.displayed_name }}</v-btn> <v-btn flat @click="$router.push({name: 'Account', params: { name: getUser().actor.username }})" v-if="$store.state.user">{{ this.displayed_name }}</v-btn>
<v-btn v-else :to="{ name: 'Login' }">Se connecter</v-btn>
</v-toolbar> </v-toolbar>
</template> </template>
@ -107,9 +111,9 @@
val && this.querySelections(val) val && this.querySelections(val)
}, },
searchSelect(val) { searchSelect(val) {
console.log(val); console.log('searchSelect', val);
if (val.type === 'Event') { if (val.type === 'Event') {
this.$router.push({name: 'Event', params: { name: val.organizer.username, slug: val.slug }}); this.$router.push({name: 'Event', params: { uuid: val.uuid }});
} else if (val.type === 'Locality') { } else if (val.type === 'Locality') {
this.$router.push({name: 'EventList', params: {location: val.geohash}}); this.$router.push({name: 'EventList', params: {location: val.geohash}});
} else { } else {

View file

@ -9,6 +9,7 @@ defmodule Eventos.Events do
alias Eventos.Events.Event alias Eventos.Events.Event
alias Eventos.Events.Comment alias Eventos.Events.Comment
alias Eventos.Actors.Actor alias Eventos.Actors.Actor
alias Eventos.Addresses.Address
@doc """ @doc """
Returns the list of events. Returns the list of events.
@ -54,6 +55,19 @@ defmodule Eventos.Events do
) )
end end
import Geo.PostGIS
def find_close_events(lon, lat, radius \\ 50000) do # 50 000 meters -> 50 kms
ip_point = Geo.WKT.decode("SRID=4326;POINT(#{lon} #{lat})")
Repo.all(
from e in Event,
join: a in Address,
on: a.id == e.physical_address_id,
where: st_dwithin_in_meters(^ip_point, a.geom, ^radius),
preload: :organizer_actor
)
end
@doc """ @doc """
Gets a single event. Gets a single event.

View file

@ -7,15 +7,24 @@ defmodule EventosWeb.EventController do
alias Eventos.Events alias Eventos.Events
alias Eventos.Events.Event alias Eventos.Events.Event
alias Eventos.Export.ICalendar alias Eventos.Export.ICalendar
alias Eventos.Addresses
import Logger require Logger
action_fallback EventosWeb.FallbackController action_fallback EventosWeb.FallbackController
def index(conn, _params) do def index(conn, _params) do
events = Events.list_events() ip = "88.161.154.97"
render(conn, "index.json", events: events) Logger.debug(inspect Geolix.lookup(ip), pretty: true)
with %{city: %Geolix.Result.City{city: city, country: country, location: %Geolix.Record.Location{latitude: latitude, longitude: longitude}}} <- Geolix.lookup(ip) do
Logger.debug(inspect city)
Logger.debug(inspect [latitude, longitude])
distance = case city do
nil -> 500000
_ -> 50000
end
events = Events.find_close_events(longitude, latitude, distance)
render(conn, "index.json", events: events, coord: %{longitude: longitude, latitude: latitude, distance: distance}, city: city, country: country)
end
end end
def create(conn, %{"event" => event_params}) do def create(conn, %{"event" => event_params}) do

View file

@ -0,0 +1,16 @@
defmodule EventosWeb.ParticipantController do
@moduledoc """
Controller for participants to an event
"""
use EventosWeb, :controller
alias Eventos.Events
def join(conn, %{"uuid" => uuid}) do
with event <- Events.get_event_by_uuid(uuid),
%{actor: actor} <- Guardian.Plug.current_resource(conn) do
participant = Events.create_participant(%{"event_id" => event.id, "actor_id" => actor.id, "role" => 1})
render(conn, "participant.json", %{participant: participant})
end
end
end

View file

@ -81,14 +81,12 @@ defmodule EventosWeb.Router do
patch "/events/:uuid", EventController, :update patch "/events/:uuid", EventController, :update
put "/events/:uuid", EventController, :update put "/events/:uuid", EventController, :update
delete "/events/:uuid", EventController, :delete delete "/events/:uuid", EventController, :delete
post "/events/:uuid/join", ParticipantController, :join
post "/comments", CommentController, :create post "/comments", CommentController, :create
patch "/comments/:uuid", CommentController, :update patch "/comments/:uuid", CommentController, :update
put "/comments/:uuid", CommentController, :update put "/comments/:uuid", CommentController, :update
delete "/comments/:uuid", CommentController, :delete delete "/comments/:uuid", CommentController, :delete
#post "/events/:id/request", EventRequestController, :create_for_event
resources "/participant", ParticipantController
resources "/bots", BotController, except: [:new, :edit, :show, :index] resources "/bots", BotController, except: [:new, :edit, :show, :index]
#resources "/requests", EventRequestController
post "/groups", GroupController, :create post "/groups", GroupController, :create
post "/groups/:name/join", GroupController, :join post "/groups/:name/join", GroupController, :join
resources "/members", MemberController resources "/members", MemberController

View file

@ -5,8 +5,13 @@ defmodule EventosWeb.EventView do
use EventosWeb, :view use EventosWeb, :view
alias EventosWeb.{EventView, ActorView, GroupView, AddressView} alias EventosWeb.{EventView, ActorView, GroupView, AddressView}
def render("index.json", %{events: events}) do def render("index.json", %{events: events, coord: coord, city: city, country: country}) do
%{data: render_many(events, EventView, "event_simple.json")} %{
data: render_many(events, EventView, "event_simple.json"),
coord: coord,
city: city,
country: country,
}
end end
def render("show_simple.json", %{event: event}) do def render("show_simple.json", %{event: event}) do
@ -49,7 +54,7 @@ defmodule EventosWeb.EventView do
ends_on: event.ends_on, ends_on: event.ends_on,
uuid: event.uuid, uuid: event.uuid,
organizer: render_one(event.organizer_actor, ActorView, "acccount_basic.json"), organizer: render_one(event.organizer_actor, ActorView, "acccount_basic.json"),
participants: render_many(event.participants, ActorView, "show_basic.json"), participants: render_many(event.participants, ActorView, "acccount_basic.json"),
physical_address: render_one(event.physical_address, AddressView, "address.json"), physical_address: render_one(event.physical_address, AddressView, "address.json"),
type: "Event", type: "Event",
address_type: event.address_type, address_type: event.address_type,

View file

@ -26,7 +26,7 @@ defmodule Eventos.Mixfile do
def application do def application do
[ [
mod: {Eventos.Application, []}, mod: {Eventos.Application, []},
extra_applications: [:logger, :runtime_tools, :guardian, :bamboo] extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :geolix]
] ]
end end
@ -69,6 +69,7 @@ defmodule Eventos.Mixfile do
{:ex_ical, github: "fazibear/ex_ical"}, {:ex_ical, github: "fazibear/ex_ical"},
{:bamboo, "~> 1.0"}, {:bamboo, "~> 1.0"},
{:bamboo_smtp, "~> 1.5.0"}, {:bamboo_smtp, "~> 1.5.0"},
{:geolix, "~> 0.16"},
# Dev and test dependencies # Dev and test dependencies
{:phoenix_live_reload, "~> 1.0", only: :dev}, {:phoenix_live_reload, "~> 1.0", only: :dev},
{:ex_machina, "~> 2.1", only: :test}, {:ex_machina, "~> 2.1", only: :test},

View file

@ -31,6 +31,7 @@
"gen_smtp": {:hex, :gen_smtp, "0.12.0", "97d44903f5ca18ca85cb39aee7d9c77e98d79804bbdef56078adcf905cb2ef00", [:rebar3], [], "hexpm"}, "gen_smtp": {:hex, :gen_smtp, "0.12.0", "97d44903f5ca18ca85cb39aee7d9c77e98d79804bbdef56078adcf905cb2ef00", [:rebar3], [], "hexpm"},
"geo": {:hex, :geo, "2.1.0", "f9a7a1403dde669c4e3f1885aeb4f3b3fb4e51cd28ada6d9f97463e5da65c04a", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "geo": {:hex, :geo, "2.1.0", "f9a7a1403dde669c4e3f1885aeb4f3b3fb4e51cd28ada6d9f97463e5da65c04a", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"geo_postgis": {:hex, :geo_postgis, "1.1.0", "4c9efc082a8b625c335967fec9f5671c2bc8a0a686f9c5130445ebbcca989740", [:mix], [{:geo, "~> 2.0", [hex: :geo, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"}, "geo_postgis": {:hex, :geo_postgis, "1.1.0", "4c9efc082a8b625c335967fec9f5671c2bc8a0a686f9c5130445ebbcca989740", [:mix], [{:geo, "~> 2.0", [hex: :geo, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"},
"geolix": {:hex, :geolix, "0.16.0", "0095f5e828c0b46596216efaf6a4d46aba3f1a4da3bdfec735b2e49c8cd908fb", [:mix], [{:mmdb2_decoder, "~> 0.2.0", [hex: :mmdb2_decoder, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.0", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"guardian": {:hex, :guardian, "1.0.1", "db0fbaf571c3b874785818b7272eaf5f1ed97a2f9b1f8bc5dc8b0fb8f8f7bb06", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, "guardian": {:hex, :guardian, "1.0.1", "db0fbaf571c3b874785818b7272eaf5f1ed97a2f9b1f8bc5dc8b0fb8f8f7bb06", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
"guardian_db": {:hex, :guardian_db, "1.1.0", "45ab94206cce38f7443dc27de6dc52966ccbdeff65ca1b1f11a6d8f3daceb556", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, "guardian_db": {:hex, :guardian_db, "1.1.0", "45ab94206cce38f7443dc27de6dc52966ccbdeff65ca1b1f11a6d8f3daceb556", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
@ -48,6 +49,7 @@
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mix_test_watch": {:hex, :mix_test_watch, "0.6.0", "5e206ed04860555a455de2983937efd3ce79f42bd8536fc6b900cc286f5bb830", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"}, "mix_test_watch": {:hex, :mix_test_watch, "0.6.0", "5e206ed04860555a455de2983937efd3ce79f42bd8536fc6b900cc286f5bb830", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
"mmdb2_decoder": {:hex, :mmdb2_decoder, "0.2.0", "34346c43bb1860d38d5505e5edf3d5e31a0d9fce4ee48f0c8c762875bcd05bb2", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},

View file

@ -0,0 +1,15 @@
defmodule Eventos.Repo.Migrations.AlterEventsSetDescriptionAsText do
use Ecto.Migration
def up do
alter table(:events) do
modify :description, :text
end
end
def down do
alter table(:events) do
modify :description, :string
end
end
end