parent
b93cf91dbd
commit
7111b4578b
31 changed files with 270 additions and 70 deletions
|
@ -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)
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
19
app/cron.js
19
app/cron.js
|
@ -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)
|
||||||
await sendNotification(notification, event, e)
|
try {
|
||||||
e.destroy()
|
await sendNotification(notification, event, e)
|
||||||
|
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()
|
||||||
|
|
4
app/emails/confirm/html.pug
Normal file
4
app/emails/confirm/html.pug
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
p= t('confirm_email')
|
||||||
|
|
||||||
|
hr
|
||||||
|
small #{config.baseurl}
|
|
@ -1,6 +1,4 @@
|
||||||
h4 Gancio
|
|
||||||
|
|
||||||
p= t('registration_email')
|
p= t('registration_email')
|
||||||
|
|
||||||
small --
|
hr
|
||||||
small https://cisti.org
|
small #{config.baseurl}
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"registration_email": "registration_email"
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"registration_email": "Ciao, la tua registrazione sarà confermata nei prossimi giorni. Riceverai una conferma non temere."
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"registration_email": "registration_email"
|
|
||||||
}
|
|
26
app/migrations/20190316230454-notification_status.js
Normal file
26
app/migrations/20190316230454-notification_status.js
Normal 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')
|
||||||
|
}
|
||||||
|
};
|
|
@ -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
37
app/models/index.js
Normal 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;
|
|
@ -1,3 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'extends': 'standard'
|
'extends': 'standard',
|
||||||
|
"rules": {
|
||||||
|
"camelcase": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
25
client/src/components/About.vue
Normal file
25
client/src/components/About.vue
Normal 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>
|
|
@ -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: {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
const user = await api.login(this.email, this.password)
|
try {
|
||||||
if (!user) {
|
const user = await api.login(this.email, this.password)
|
||||||
return;
|
if (!user) {
|
||||||
|
Message({ message: this.$t('login error'), type: 'error' })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.login(user)
|
||||||
|
Message({ message: this.$t('Logged'), type: 'success' })
|
||||||
|
} catch (e) {
|
||||||
|
Message({ message: this.$t('login error'), type: 'error' })
|
||||||
}
|
}
|
||||||
this.login(user)
|
|
||||||
this.email = this.password = ''
|
this.email = this.password = ''
|
||||||
this.$router.go(-1)
|
this.$router.replace("/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,35 +1,40 @@
|
||||||
<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-option(v-for='place in places_name' :label='place' :value='place' :key='place.id')
|
||||||
el-select(v-model='event.place.name' @change='placeChoosed' filterable allow-create default-first-option)
|
div {{$t("Address")}}
|
||||||
el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id')
|
el-input.mb-3(ref='address' v-model='event.place.address' @keydown.native.enter='next')
|
||||||
el-form-item(:label='$t("Address")')
|
el-button.float-right(@click='next' :disabled='!couldProceed') {{$t('Next')}}
|
||||||
el-input(ref='address' v-model='event.place.address' @keydown.native.enter='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')}}
|
||||||
|
|
||||||
el-tab-pane
|
el-tab-pane
|
||||||
span(slot='label') {{$t('What')}} <v-icon name='file-alt'/>
|
span(slot='label') {{$t('What')}} <v-icon name='file-alt'/>
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.`,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
4
locales/en.json
Normal 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
4
locales/es.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"registration_email": "registration_email",
|
||||||
|
"confirm_email": "confirm_email"
|
||||||
|
}
|
4
locales/it.json
Normal file
4
locales/it.json
Normal 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
4
locales/zh.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"registration_email": "registration_email",
|
||||||
|
"confirm_email": "confirm_email"
|
||||||
|
}
|
Loading…
Reference in a new issue