fix #27, fix #25, fix #26, fix #20, fix #19

This commit is contained in:
lesion 2019-03-18 01:42:42 +01:00
parent b93cf91dbd
commit 7111b4578b
31 changed files with 270 additions and 70 deletions

View file

@ -61,6 +61,7 @@ api.get('/event/:event_id', eventController.get)
// confirm event // confirm event
api.get('/event/confirm/:event_id', isAuth, isAdmin, eventController.confirm) api.get('/event/confirm/:event_id', isAuth, isAdmin, eventController.confirm)
api.get('/event/unconfirm/:event_id', isAuth, isAdmin, eventController.unconfirm)
// export events (rss/ics) // export events (rss/ics)
api.get('/export/:type', exportController.export) api.get('/export/:type', exportController.export)

View file

@ -82,6 +82,18 @@ const eventController = {
} }
}, },
async unconfirm (req, res) {
const id = req.params.event_id
const event = await Event.findByPk(id)
try {
await event.update({ is_visible: false })
res.send(200)
} catch (e) {
res.send(404)
}
},
async getUnconfirmed (req, res) { async getUnconfirmed (req, res) {
const events = await Event.findAll({ const events = await Event.findAll({
where: { where: {

View file

@ -198,6 +198,9 @@ const userController = {
async update (req, res) { async update (req, res) {
const user = await User.findByPk(req.body.id) const user = await User.findByPk(req.body.id)
if (user) { if (user) {
if (!user.is_active && req.body.is_active) {
await mail.send(user.email, 'confirm', { user, config })
}
await user.update(req.body) await user.update(req.body)
res.json(user) res.json(user)
} else { } else {
@ -209,7 +212,7 @@ const userController = {
const n_users = await User.count() const n_users = await User.count()
try { try {
if (n_users === 0) { if (n_users === 0) {
// admin will be the first registered user // the first registered user will be an active admin
req.body.is_active = req.body.is_admin = true req.body.is_active = req.body.is_admin = true
} else { } else {
req.body.is_active = false req.body.is_active = false

View file

@ -14,7 +14,7 @@ async function sendNotification (notification, event, eventNotification) {
const p = mail.send(notification.email, 'event', { event }) const p = mail.send(notification.email, 'event', { event })
promises.push(p) promises.push(p)
break break
case 'mail_admin': case 'admin_email':
const admins = await User.findAll({ where: { is_admin: true } }) const admins = await User.findAll({ where: { is_admin: true } })
promises.push(admins.map(admin => promises.push(admins.map(admin =>
mail.send(admin.email, 'event', { event, to_confirm: true, notification }))) mail.send(admin.email, 'event', { event, to_confirm: true, notification })))
@ -26,7 +26,7 @@ async function sendNotification (notification, event, eventNotification) {
promises.push(b) promises.push(b)
} }
// user publish // user publish
if (event.user && event.user.mastodon_auth) { if (event.user && event.user.mastodon_auth && event.user.mastodon_auth.access_token) {
const b = bot.post(event.user.mastodon_auth, event).then(ret => { const b = bot.post(event.user.mastodon_auth, event).then(ret => {
event.activitypub_id = ret.id event.activitypub_id = ret.id
return event.save() return event.save()
@ -45,17 +45,24 @@ async function sendNotification (notification, event, eventNotification) {
async function loop () { async function loop () {
settings = await settingsController.settings() settings = await settingsController.settings()
// get all event notification in queue // get all event notification in queue
const eventNotifications = await EventNotification.findAll() const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
const promises = eventNotifications.map(async e => { const promises = eventNotifications.map(async e => {
const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] }) const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] })
if (!event.place) return if (!event.place) return
const notification = await Notification.findByPk(e.notificationId) const notification = await Notification.findByPk(e.notificationId)
try {
await sendNotification(notification, event, e) await sendNotification(notification, event, e)
e.destroy() e.status = 'sent'
e.save()
} catch (e) {
console.error(e)
e.status = 'error'
return e.save()
}
}) })
return Promise.all(promises) return Promise.all(promises)
} }
setInterval(loop, 20000) setInterval(loop, 260000)
loop() loop()

View file

@ -0,0 +1,4 @@
p= t('confirm_email')
hr
small #{config.baseurl}

View file

@ -1,6 +1,4 @@
h4 Gancio
p= t('registration_email') p= t('registration_email')
small -- hr
small https://cisti.org small #{config.baseurl}

View file

@ -1,3 +0,0 @@
{
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
}

View file

@ -1,3 +0,0 @@
{
"registration_email": "registration_email"
}

View file

@ -1,3 +0,0 @@
{
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
}

View file

@ -1,3 +0,0 @@
{
"registration_email": "registration_email"
}

View file

@ -0,0 +1,26 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
return queryInterface.addColumn('EventNotifications', 'status',
{ type: Sequelize.ENUM, values: ['new', 'sent', 'error'], index: true, defaultValue: 'new' })
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.dropTable('users');
*/
return queryInterface.removeColumn('EventNotifications', 'status')
}
};

View file

@ -48,7 +48,15 @@ Event.hasMany(Comment)
Event.belongsToMany(Tag, { through: 'tagEvent' }) Event.belongsToMany(Tag, { through: 'tagEvent' })
Tag.belongsToMany(Event, { through: 'tagEvent' }) Tag.belongsToMany(Event, { through: 'tagEvent' })
const EventNotification = db.define('EventNotification') const EventNotification = db.define('EventNotification', {
status: {
type: Sequelize.ENUM,
values: ['new', 'sent', 'error'],
defaultValue: 'new',
index: true
}
})
Event.belongsToMany(Notification, { through: EventNotification }) Event.belongsToMany(Notification, { through: EventNotification })
Notification.belongsToMany(Event, { through: EventNotification }) Notification.belongsToMany(Event, { through: EventNotification })

37
app/models/index.js Normal file
View file

@ -0,0 +1,37 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

View file

@ -1,3 +1,6 @@
module.exports = { module.exports = {
'extends': 'standard' 'extends': 'standard',
"rules": {
"camelcase": 0
}
} }

View file

@ -18,6 +18,8 @@ function get (path) {
store.commit('logout') store.commit('logout')
return false return false
} }
throw e.response && e.response.data &&
e.response.data.errors && e.response.data.errors[0].message
}) })
} }
@ -29,6 +31,8 @@ function post (path, data) {
store.commit('logout') store.commit('logout')
return false return false
} }
throw e.response && e.response.data &&
e.response.data.errors && e.response.data.errors[0].message
}) })
} }
function put (path, data) { function put (path, data) {
@ -43,12 +47,18 @@ function del (path) {
export default { export default {
login: (email, password) => post('/login', { email, password }), login: (email, password) => post('/login', { email, password }),
register: user => post('/user', user), register: user => post('/user', user),
getAllEvents: (month, year) => get(`/event/${year}/${month}/`), getAllEvents: (month, year) => get(`/event/${year}/${month}/`),
getUnconfirmedEvents: () => get('/event/unconfirmed'), getUnconfirmedEvents: () => get('/event/unconfirmed'),
confirmEvent: id => get(`/event/confirm/${id}`), confirmEvent: id => get(`/event/confirm/${id}`),
unconfirmEvent: id => get(`/event/unconfirm/${id}`),
addNotification: notification => post('/event/notification', notification), addNotification: notification => post('/event/notification', notification),
addEvent: event => post('/user/event', event), addEvent: event => post('/user/event', event),
updateEvent: event => put('/user/event', event), updateEvent: event => put('/user/event', event),
updatePlace: place => put('/place', place), updatePlace: place => put('/place', place),
delEvent: eventId => del(`/user/event/${eventId}`), delEvent: eventId => del(`/user/event/${eventId}`),
getEvent: eventId => get(`/event/${eventId}`), getEvent: eventId => get(`/event/${eventId}`),
@ -59,8 +69,6 @@ export default {
updateUser: user => put('/user', user), updateUser: user => put('/user', user),
getAuthURL: mastodonInstance => post('/user/getauthurl', mastodonInstance), getAuthURL: mastodonInstance => post('/user/getauthurl', mastodonInstance),
setCode: code => post('/user/code', code), setCode: code => post('/user/code', code),
getKnowLocations: () => get('/locations'),
getKnowTags: () => get('/tags'),
getAdminSettings: () => get('/settings') getAdminSettings: () => get('/settings')
// setAdminSetting: (key, value) => post('/settings', { key, value }) // setAdminSetting: (key, value) => post('/settings', { key, value })
} }

View file

@ -0,0 +1,25 @@
<template lang="pug">
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("About")'
:visible='true' size='lg')
h5 Chi siamo
p.
Gancio e' un progetto dell'<a href='https://autistici.org/underscore'>underscore hacklab</a> e uno dei
servizi di <a href='https://cisti.org'>cisti.org</a>.
h5 Ok, ma cosa vuol dire?
blockquote.
Se vieni a Torino e dici: "ehi, ci diamo un gancio alle 8?" nessuno si presenterà con i guantoni per fare a mazzate.
Darsi un gancio vuol dire beccarsi alle ore X in un posto Y
p
small A: a che ora è il gancio in radio per andare al presidio?
p
small B: non so ma domani non posso venire, ho gia' un gancio per caricare il bar.
h5 Contatti
p.
Hai scritto una nuova interfaccia per gancio? Vuoi aprire un nuovo nodo di gancio nella tua città?
C'è qualcosa che vorresti migliorare? Aiuti e suggerimenti sono sempre benvenuti, puoi scriverci
su underscore chicciola autistici.org
</template>

View file

@ -1,16 +1,26 @@
<template lang="pug"> <template lang="pug">
b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")' :visible='true' size='lg') b-modal(hide-footer @hidden='$router.replace("/")' :title='$t("Admin")'
:visible='true' size='lg')
el-tabs(tabPosition='left' v-model='tab') el-tabs(tabPosition='left' v-model='tab')
//- USERS //- USERS
el-tab-pane.pt-1 el-tab-pane.pt-1
template(slot='label') template(slot='label')
v-icon(name='users') v-icon(name='users')
span.ml-1 {{$t('Users')}} span.ml-1 {{$t('Users')}}
b-table(:items='users' :fields='userFields' striped small hover el-table(:data='paginatedUsers' small)
:per-page='5' :current-page='userPage') el-table-column(label='Email')
template(slot='action' slot-scope='data') template(slot-scope='data')
el-button.mr-1(size='mini' :type='data.item.is_active?"warning":"success"' @click='toggle(data.item)') {{data.item.is_active?$t('Deactivate'):$t('Activate')}} el-popover(trigger='hover' :content='data.row.description' width='400')
el-button(size='mini' :type='data.item.is_admin?"danger":"warning"' @click='toggleAdmin(data.item)') {{data.item.is_admin?$t('Remove Admin'):$t('Admin')}} span(slot='reference') {{data.row.email}}
el-table-column(label='Azioni')
template(slot-scope='data')
el-button.mr-1(size='mini'
:type='data.row.is_active?"warning":"success"'
@click='toggle(data.row)') {{data.row.is_active?$t('Deactivate'):$t('Activate')}}
el-button(size='mini'
:type='data.row.is_admin?"danger":"warning"'
@click='toggleAdmin(data.row)') {{data.row.is_admin?$t('Remove Admin'):$t('Admin')}}
el-pagination(:page-size='perPage' :currentPage.sync='userPage' :total='users.length') el-pagination(:page-size='perPage' :currentPage.sync='userPage' :total='users.length')
//- PLACES //- PLACES
@ -126,6 +136,10 @@ export default {
paginatedTags () { paginatedTags () {
return this.tags.slice((this.tagPage-1) * this.perPage, return this.tags.slice((this.tagPage-1) * this.perPage,
this.tagPage * this.perPage) this.tagPage * this.perPage)
},
paginatedUsers () {
return this.users.slice((this.userPage-1) * this.perPage,
this.userPage * this.perPage)
} }
}, },
methods: { methods: {

View file

@ -18,6 +18,8 @@
size='mini' :key='tag.tag') {{tag.tag}} size='mini' :key='tag.tag') {{tag.tag}}
.ml-auto(v-if='mine') .ml-auto(v-if='mine')
hr hr
el-button(v-if='event.is_visible' plain type='warning' @click.prevents='toggle' icon='el-icon-remove') {{$t('Unconfirm')}}
el-button(v-else plain type='success' @click.prevents='toggle' icon='el-icon-remove') {{$t('Confirm')}}
el-button(plain type='danger' @click.prevent='remove' icon='el-icon-remove') {{$t('Remove')}} el-button(plain type='danger' @click.prevent='remove' icon='el-icon-remove') {{$t('Remove')}}
el-button(plain type='primary' @click='$router.replace("/edit/"+event.id)') <v-icon color='orange' name='edit'/> {{$t('Edit')}} el-button(plain type='primary' @click='$router.replace("/edit/"+event.id)') <v-icon color='orange' name='edit'/> {{$t('Edit')}}
@ -75,6 +77,20 @@ export default {
await api.delEvent(this.event.id) await api.delEvent(this.event.id)
this.delEvent(this.event.id) this.delEvent(this.event.id)
this.$refs.eventDetail.hide() this.$refs.eventDetail.hide()
},
async toggle () {
try {
if (this.event.is_visible) {
await api.unconfirmEvent(this.id)
this.event.is_visible = false
} else {
await api.confirmEvent(this.id)
this.event.is_visible = true
}
} catch (e) {
}
} }
} }
} }

View file

@ -15,7 +15,8 @@
<script> <script>
import api from '@/api' import api from '@/api'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import { log } from 'util'; import { Message } from 'element-ui'
export default { export default {
name: 'Login', name: 'Login',
@ -29,13 +30,19 @@ export default {
...mapActions(['login']), ...mapActions(['login']),
async submit (e) { async submit (e) {
e.preventDefault() e.preventDefault()
try {
const user = await api.login(this.email, this.password) const user = await api.login(this.email, this.password)
if (!user) { if (!user) {
Message({ message: this.$t('login error'), type: 'error' })
return; return;
} }
this.login(user) this.login(user)
Message({ message: this.$t('Logged'), type: 'success' })
} catch (e) {
Message({ message: this.$t('login error'), type: 'error' })
}
this.email = this.password = '' this.email = this.password = ''
this.$router.go(-1) this.$router.replace("/")
} }
} }
} }

View file

@ -14,8 +14,12 @@
span.d-md-none {{$t('Admin')}} span.d-md-none {{$t('Admin')}}
b-nav-item(to='/export' v-b-tooltip :title='$t("Export")') <v-icon name='file-export' color='yellow'/> b-nav-item(to='/export' v-b-tooltip :title='$t("Export")') <v-icon name='file-export' color='yellow'/>
span.d-md-none {{$t('Export')}} span.d-md-none {{$t('Export')}}
b-nav-item(v-if='logged' variant='danger' @click='logout' v-b-tooltip :title='$t("Logout")') <v-icon color='red' name='sign-out-alt'/> b-nav-item(v-if='logged' @click='logout' v-b-tooltip :title='$t("Logout")') <v-icon color='red' name='sign-out-alt'/>
span.d-md-none {{$t('Logout')}} span.d-md-none {{$t('Logout')}}
b-navbar-nav.ml-auto
b-nav-item(to='/about')
span {{$t('Info')}} <v-icon color='#ff9fc4' name='question-circle'/>
</template> </template>
<script> <script>
import {mapState, mapActions} from 'vuex' import {mapState, mapActions} from 'vuex'

View file

@ -35,8 +35,8 @@ export default {
async register () { async register () {
try { try {
const user = await api.register(this.user) const user = await api.register(this.user)
this.$refs.modal.hide()
if (!user.is_admin) { if (!user.is_admin) {
this.$refs.modal.hide()
Message({ Message({
message: this.$t('registration_complete'), message: this.$t('registration_complete'),
type: 'success' type: 'success'
@ -48,6 +48,10 @@ export default {
}) })
} }
} catch (e) { } catch (e) {
Message({
message: e,
type: 'error'
})
console.error(e) console.error(e)
} }
} }

View file

@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
b-modal(:title="$t('Settings')" hide-footer @hide='$router.go(-1)' :visible='true') b-modal(:title="$t('Settings')" hide-footer @hidden='$router.replace("/")' :visible='true')
el-form(inline) el-form(inline)
el-input(v-model="mastodon_instance" type='success') el-input(v-model="mastodon_instance" type='success')
span(slot='prepend') Mastodon instance span(slot='prepend') Mastodon instance

View file

@ -1,32 +1,37 @@
<template lang="pug"> <template lang="pug">
b-modal(ref='modal' @hidden='$router.replace("/")' size='md' :visible='true' b-modal(ref='modal' @hidden='$router.replace("/")' size='lg' :visible='true'
:title="edit?$t('Edit event'):$t('New event')" hide-footer) :title="edit?$t('Edit event'):$t('New event')" hide-footer)
b-container b-container
el-tabs.mb-2(v-model='activeTab' v-loading='sending' @tab-click.native='changeTab') el-tabs.mb-2(v-model='activeTab' v-loading='sending' @tab-click.native='changeTab')
//- NOT LOGGED EVENT
el-tab-pane(v-show='!user')
span(slot='label') {{$t('anon_newevent')}} <v-icon name='user-secret'/>
p(v-html="$t('anon_newevent_explanation')")
el-button.float-right(@click='next' :disabled='!couldProceed') Mi sento in colpa
//- WHERE
el-tab-pane el-tab-pane
span(slot='label') {{$t('Where')}} <v-icon name='map-marker-alt'/> span(slot='label') {{$t('Where')}} <v-icon name='map-marker-alt'/>
p {{$t('where_explanation')}} div {{$t('where_explanation')}}
el-form(label-width='120px') el-select.mb-3(v-model='event.place.name' @change='placeChoosed' filterable allow-create default-first-option)
el-form-item(:label='$t("Where")')
el-select(v-model='event.place.name' @change='placeChoosed' filterable allow-create default-first-option)
el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id') el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id')
el-form-item(:label='$t("Address")') div {{$t("Address")}}
el-input(ref='address' v-model='event.place.address' @keydown.native.enter='next') el-input.mb-3(ref='address' v-model='event.place.address' @keydown.native.enter='next')
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}} el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
//- WHEN
el-tab-pane el-tab-pane
span(slot='label') {{$t('When')}} <v-icon name='clock'/> span(slot='label') {{$t('When')}} <v-icon name='clock'/>
el-form(label-width='120px')
span {{event.multidate ? $t('dates_explanation') : $t('date_explanation')}} span {{event.multidate ? $t('dates_explanation') : $t('date_explanation')}}
el-switch.float-right(v-model='event.multidate' :active-text="$t('multidate_explanation')") el-switch.float-right(v-model='event.multidate' :active-text="$t('multidate_explanation')")
v-date-picker.mb-3(:mode='event.multidate ? "range" : "single"' v-model='date' is-inline v-date-picker.mb-3(:mode='event.multidate ? "range" : "single"' v-model='date' is-inline
is-expanded :min-date='new Date()' @input='date ? $refs.time_start.focus() : false') is-expanded :min-date='new Date()' @input='date ? $refs.time_start.focus() : false')
el-form-item(:label="$t('time_start_explanation')") div {{$t('time_start_explanation')}}
el-time-select(ref='time_start' el-time-select.mb-3(ref='time_start'
v-model="time.start" v-model="time.start"
:picker-options="{ start: '00:00', step: '00:30', end: '24:00'}") :picker-options="{ start: '00:00', step: '00:30', end: '24:00'}")
el-form-item(:label="$t('time_end_explanation')") div {{$t('time_end_explanation')}}
el-time-select(v-model='time.end' el-time-select(v-model='time.end'
:picker-options="{start: '00:00', step: '00:30', end: '24:00'}") :picker-options="{start: '00:00', step: '00:30', end: '24:00'}")
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}} el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
@ -111,20 +116,23 @@ export default {
...mapState({ ...mapState({
tags: state => state.tags.map(t => t.tag ), tags: state => state.tags.map(t => t.tag ),
places_name: state => state.places.map(p => p.name ), places_name: state => state.places.map(p => p.name ),
places: state => state.places places: state => state.places,
user: state => state.user
}), }),
couldProceed () { couldProceed () {
switch(Number(this.activeTab)) { switch(Number(this.activeTab)) {
case 0: case 0:
return true
case 1:
return this.event.place.name.length>0 && return this.event.place.name.length>0 &&
this.event.place.address.length>0 this.event.place.address.length>0
case 1: case 2:
if (this.date && this.time.start) return true if (this.date && this.time.start) return true
break break
case 2: case 3:
return this.event.title.length>0 return this.event.title.length>0
break break
case 3: case 4:
return true return true
break break
} }

View file

@ -77,6 +77,8 @@ const it = {
'admin_place_explanation': 'Puoi modificare i luoghi inseriti', 'admin_place_explanation': 'Puoi modificare i luoghi inseriti',
'Edit event': 'Modifica evento', 'Edit event': 'Modifica evento',
'New event': 'Nuovo evento', 'New event': 'Nuovo evento',
anon_newevent: 'Evento senza autore',
anon_newevent_explanation: `Puoi inserire un evento senza registrarti o fare il login, ma in questo caso dovrai aspettare che qualcuno lo legga confermando che si tratta di un evento adatto a questo spazio, delegando questa scelta.<br/><br/>Ti consigliamo quindi di fare il <a href="/login">login</a> o di <a href="/register">registrarti</a>, ma se vuoi continuare fai pure (ti devi sentire un po' in colpa però).`,
'Insert your address': 'Inserisci il tuo indirizzo', 'Insert your address': 'Inserisci il tuo indirizzo',
registration_complete: 'Controlla la tua posta (anche la cartella spam)', registration_complete: 'Controlla la tua posta (anche la cartella spam)',
registration_email: `Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.`, registration_email: `Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.`,

View file

@ -19,6 +19,8 @@ import 'vue-awesome/icons/users'
import 'vue-awesome/icons/calendar' import 'vue-awesome/icons/calendar'
import 'vue-awesome/icons/edit' import 'vue-awesome/icons/edit'
import 'vue-awesome/icons/envelope-open-text' import 'vue-awesome/icons/envelope-open-text'
import 'vue-awesome/icons/user-secret'
import 'vue-awesome/icons/question-circle'
import Icon from 'vue-awesome/components/Icon' import Icon from 'vue-awesome/components/Icon'
@ -30,7 +32,7 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
import { Button, Select, Tag, Option, Table, FormItem, Card, import { Button, Select, Tag, Option, Table, FormItem, Card,
Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect, Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect,
TableColumn, ColorPicker, Pagination } from 'element-ui' TableColumn, ColorPicker, Pagination, Popover } from 'element-ui'
import ElementLocale from 'element-ui/lib/locale' import ElementLocale from 'element-ui/lib/locale'
import MagicGrid from 'vue-magic-grid' import MagicGrid from 'vue-magic-grid'
@ -49,6 +51,7 @@ import itLocale from '@/locale/it'
import enLocale from '@/locale/en' import enLocale from '@/locale/en'
Vue.use(Button) Vue.use(Button)
Vue.use(Popover)
Vue.use(Card) Vue.use(Card)
Vue.use(Select) Vue.use(Select)
Vue.use(Tag) Vue.use(Tag)

View file

@ -8,6 +8,7 @@ import Login from './components/Login'
import Register from './components/Register' import Register from './components/Register'
import Export from './components/Export' import Export from './components/Export'
import Admin from './components/Admin' import Admin from './components/Admin'
import About from './components/About'
Vue.use(Router) Vue.use(Router)
@ -50,6 +51,10 @@ export default new Router({
{ {
path: '/admin/oauth', path: '/admin/oauth',
components: { modal: Admin } components: { modal: Admin }
},
{
path: '/about',
components: { modal: About }
} }
] ]
}) })

View file

@ -17,7 +17,8 @@ export default new Vuex.Store({
token: state => state.token, token: state => state.token,
filteredEvents: state => { filteredEvents: state => {
const events = state.events.map(e => { const events = state.events.map(e => {
const past = (moment().diff(e.start_datetime, 'minutes') > 0) const end_datetime = e.end_datetime || moment(e.start_datetime).add('3', 'hour')
const past = (moment().diff(end_datetime, 'minutes') > 0)
e.past = past e.past = past
return e return e
}) })

4
locales/en.json Normal file
View file

@ -0,0 +1,4 @@
{
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.",
"confirm_email": "confirm_email"
}

4
locales/es.json Normal file
View file

@ -0,0 +1,4 @@
{
"registration_email": "registration_email",
"confirm_email": "confirm_email"
}

4
locales/it.json Normal file
View file

@ -0,0 +1,4 @@
{
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere.",
"confirm_email": "Ti abbiamo attivato il tuo utente su gancio. Ora puoi aggiungere eventi."
}

4
locales/zh.json Normal file
View file

@ -0,0 +1,4 @@
{
"registration_email": "registration_email",
"confirm_email": "confirm_email"
}