Commit 41978319 authored by Dimitri von Rütte's avatar Dimitri von Rütte
Browse files

Merge branch 'master' into feature/addpasswordfield

parents 56296d75 d29d44e1
stages: stages:
- test
- build - build
- deploy - deploy
eslint:
stage: test
image: node:latest
before_script:
- npm install
script:
- npm run lint
build_master_dev: build_master_dev:
stage: build stage: build
image: docker:latest image: docker:latest
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"start": "webpack-dev-server --hot --inline", "start": "webpack-dev-server --hot --inline",
"build": "webpack -p --config webpack.config.prod.js", "build": "webpack -p --config webpack.config.prod.js",
"build-dev": "webpack -p --config webpack.config.dev.js", "build-dev": "webpack -p --config webpack.config.dev.js",
"lint": "eslint src/**" "lint": "eslint src/**.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
"@material/drawer": "^0.30.0", "@material/drawer": "^0.30.0",
"@material/select": "^0.35.1", "@material/select": "^0.35.1",
"ajv": "^5.5.0", "ajv": "^5.5.0",
"amiv-web-ui-components": "git+https://git@gitlab.ethz.ch/amiv/web-ui-components.git", "amiv-web-ui-components": "git+https://git@gitlab.ethz.ch/amiv/web-ui-components.git#61e129378f57e98d3f60106298251c3b0f4b2286",
"axios": "^0.17.1", "axios": "^0.17.1",
"client-oauth2": "^4.2.0", "client-oauth2": "^4.2.0",
"mithril": "^1.1.6", "mithril": "^1.1.6",
......
import tool from 'announcetool';
const m = require('mithril');
export default class AnnounceTool {
oncreate() {
if (tool.wasRenderedOnce()) {
// jQuery catches the first document.ready, but afterwards we have to
// trigger a render
tool.render();
}
}
view() {
return m('div', [
m('div#tableset', [
m('p#events'),
m('div#buttonrow', [
m('button#preview.btn.btn-default', 'Preview'),
m('button#reset.btn.btn-default', 'Reset'),
m('button#send.btn.btn-default', 'Send'),
]),
]),
m('br'),
m('hr'),
m('textarea#target'),
]);
}
}
import m from 'mithril'; import m from 'mithril';
import axios from 'axios'; import axios from 'axios';
import ClientOAuth2 from 'client-oauth2'; import ClientOAuth2 from 'client-oauth2';
import { Snackbar } from 'polythene-mithril';
// eslint-disable-next-line import/extensions
import { apiUrl, ownUrl, oAuthID } from 'networkConfig'; import { apiUrl, ownUrl, oAuthID } from 'networkConfig';
import * as localStorage from './localStorage'; import * as localStorage from './localStorage';
import config from './resourceConfig.json'; import config from './resourceConfig.json';
...@@ -15,6 +17,7 @@ const APISession = { ...@@ -15,6 +17,7 @@ const APISession = {
const amivapi = axios.create({ const amivapi = axios.create({
baseURL: apiUrl, baseURL: apiUrl,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
validateStatus: () => true,
}); });
// OAuth Handler // OAuth Handler
...@@ -76,6 +79,7 @@ export function getSession() { ...@@ -76,6 +79,7 @@ export function getSession() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: APISession.token, Authorization: APISession.token,
}, },
validateStatus: () => true,
}); });
resolve(authenticatedSession); resolve(authenticatedSession);
}).catch(resetSession); }).catch(resetSession);
...@@ -116,6 +120,7 @@ export class ResourceHandler { ...@@ -116,6 +120,7 @@ export class ResourceHandler {
*/ */
constructor(resource, searchKeys = false) { constructor(resource, searchKeys = false) {
this.resource = resource; this.resource = resource;
this.rights = [];
// special case for users // special case for users
if (resource === 'users') this.searchKeys = ['firstname', 'lastname', 'nethz']; if (resource === 'users') this.searchKeys = ['firstname', 'lastname', 'nethz'];
else this.searchKeys = searchKeys || config[resource].searchKeys; else this.searchKeys = searchKeys || config[resource].searchKeys;
...@@ -176,6 +181,22 @@ export class ResourceHandler { ...@@ -176,6 +181,22 @@ export class ResourceHandler {
return `?${m.buildQueryString(fullQuery)}`; return `?${m.buildQueryString(fullQuery)}`;
} }
networkError(e) {
console.log(e);
Snackbar.show({ title: 'Network error, try again.', style: { color: 'red' } });
}
// in future, we may communicate based on the data available
// therefore, require data already here
// eslint-disable-next-line no-unused-vars
error422(data) {
Snackbar.show({ title: 'Errors in object, please fix.' });
}
successful(title) {
Snackbar.show({ title, style: { color: 'green' } });
}
get(query) { get(query) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getSession().then((api) => { getSession().then((api) => {
...@@ -184,12 +205,14 @@ export class ResourceHandler { ...@@ -184,12 +205,14 @@ export class ResourceHandler {
api.get(url).then((response) => { api.get(url).then((response) => {
if (response.status >= 400) { if (response.status >= 400) {
resetSession(); resetSession();
Snackbar.show({ title: response.data, style: { color: 'red' } });
reject(); reject();
} else { } else {
this.rights = response.data._links.self.methods;
resolve(response.data); resolve(response.data);
} }
}).catch((e) => { }).catch((e) => {
console.log(e); this.networkError(e);
reject(e); reject(e);
}); });
}); });
...@@ -208,13 +231,14 @@ export class ResourceHandler { ...@@ -208,13 +231,14 @@ export class ResourceHandler {
} }
api.get(url).then((response) => { api.get(url).then((response) => {
if (response.status >= 400) { if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
resetSession(); resetSession();
reject(); reject();
} else { } else {
resolve(response.data); resolve(response.data);
} }
}).catch((e) => { }).catch((e) => {
console.log(e); this.networkError(e);
reject(e); reject(e);
}); });
}); });
...@@ -226,17 +250,20 @@ export class ResourceHandler { ...@@ -226,17 +250,20 @@ export class ResourceHandler {
getSession().then((api) => { getSession().then((api) => {
api.post(this.resource, item).then((response) => { api.post(this.resource, item).then((response) => {
if (response.code === 201) { if (response.code === 201) {
this.successful('Creation successful.');
resolve({}); resolve({});
} else if (response.status === 422) { } else if (response.status === 422) {
this.error422(response.data);
reject(response.data); reject(response.data);
} else if (response.status >= 400) { } else if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
resetSession(); resetSession();
reject(); reject();
} else { } else {
resolve(response.data); resolve(response.data);
} }
}).catch((e) => { }).catch((e) => {
console.log(e); this.networkError(e);
reject(e); reject(e);
}); });
}); });
...@@ -265,15 +292,18 @@ export class ResourceHandler { ...@@ -265,15 +292,18 @@ export class ResourceHandler {
headers: { 'If-Match': item._etag }, headers: { 'If-Match': item._etag },
}).then((response) => { }).then((response) => {
if (response.status === 422) { if (response.status === 422) {
this.error422(response.data);
reject(response.data); reject(response.data);
} else if (response.status >= 400) { } else if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
resetSession(); resetSession();
reject(); reject();
} else { } else {
this.successful('Change successful.');
resolve(response.data); resolve(response.data);
} }
}).catch((e) => { }).catch((e) => {
console.log(e); this.networkError(e);
reject(e); reject(e);
}); });
}); });
...@@ -287,13 +317,15 @@ export class ResourceHandler { ...@@ -287,13 +317,15 @@ export class ResourceHandler {
headers: { 'If-Match': item._etag }, headers: { 'If-Match': item._etag },
}).then((response) => { }).then((response) => {
if (response.status >= 400) { if (response.status >= 400) {
Snackbar.show({ title: response.data, style: { color: 'red' } });
resetSession(); resetSession();
reject(); reject();
} else { } else {
this.successful('Delete successful.');
resolve(); resolve();
} }
}).catch((e) => { }).catch((e) => {
console.log(e); this.networkError(e);
reject(e); reject(e);
}); });
}); });
...@@ -303,11 +335,11 @@ export class ResourceHandler { ...@@ -303,11 +335,11 @@ export class ResourceHandler {
export class OauthRedirect { export class OauthRedirect {
view() { view() {
oauth.token.getToken(m.route.get()).then((response) => { oauth.token.getToken(m.route.get()).then((auth) => {
APISession.authenticated = true; APISession.authenticated = true;
APISession.token = response.accessToken; APISession.token = auth.accessToken;
localStorage.set('token', response.accessToken); localStorage.set('token', auth.accessToken);
amivapi.get(`sessions/${response.accessToken}`, { amivapi.get(`sessions/${auth.accessToken}`, {
headers: { 'Content-Type': 'application/json', Authorization: APISession.token }, headers: { 'Content-Type': 'application/json', Authorization: APISession.token },
}).then((response) => { }).then((response) => {
console.log(response); console.log(response);
......
import ItemView from './views/itemView';
import EditView from './views/editView';
import { inputGroup, selectGroup, submitButton } from './views/elements';
import TableView from './views/tableView';
import { events as config } from './config.json';
const m = require('mithril');
export class EventView extends ItemView {
constructor() {
super('events');
this.memberships = [];
}
view() {
// do not render anything if there is no data yet
if (!this.data) return m.trust('');
let comissionBadge = m('span.label.label-important', 'Who is resp of this event?');
if (this.data.membership === 'kultur') {
comissionBadge = m('span.label.label-success', 'Kulturi event');
} else if (this.data.membership === 'eestec') {
comissionBadge = m('span.label.label-important', 'EESTEC event');
} else if (this.data.membership === 'limes') {
comissionBadge = m('span.label.label-warning', 'LIMES event');
}
// TODO Question Lio171201:are we missing a "responsible" key?
const detailKeys = [
'title_de',
'rfid',
'location', 'time_start', 'time_end',
'show_website', 'catchphrase',
'time_register_start', 'price', 'allow_email_signup'];
return m('div', [
m('h1', `${this.data.title_de}`),
comissionBadge,
m('table', detailKeys.map(key => m('tr', [
m('td.detail-descriptor', config.keyDescriptors[key]),
m('td', this.data[key] ? this.data[key] : ''),
]))),
m('h2', 'Location'), m('br'),
m(TableView, {
resource: 'events',
keys: ['event.location'],
query: {
where: { user: this.id },
embedded: { group: 1 },
},
}),
m('h2', 'Signups'), m('br'),
m(TableView, {
resource: 'events',
keys: ['event.title_de'],
query: {
where: { user: this.id },
embedded: { event: 1 },
},
}),
]);
}
}
class EventEdit extends EditView {
constructor(vnode) {
super(vnode, 'events');
}
getForm() {
return m('form', [
m('div.row', [
m(inputGroup, this.bind({ title: 'Deutscher Titel', name: 'title_de' })),
m(inputGroup, this.bind({ title: 'English Title', name: 'title_en' })),
m(inputGroup, this.bind({ title: 'Location', name: 'location' })),
// m(inputGroup, this.bind({ title: 'Date-start', name: 'datetimepicker1' })),
// $('#datetimepicker1').datetimepicker();
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'May non-AMIV members register?',
name: 'allow_email_signup',
options: [true, false],
})),
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'Show on the website?',
name: 'show_website',
options: [true, false],
})),
m(selectGroup, this.bind({
classes: 'col-xs-6',
title: 'Piority from 1 to 10?',
name: 'priority',
// could be done with array.apply:
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
})),
]),
m('span', JSON.stringify(this.data)),
m('span', JSON.stringify(this.errors)),
]);
}
view() {
// do not render anything if there is no data yet
if (!this.data) return m.trust('');
return m('form', [
this.getForm(),
m(submitButton, {
active: this.valid,
args: {
onclick: this.submit('PATCH', config.patchableKeys),
class: 'btn-warning',
},
text: 'Update',
}),
]);
}
}
export class NewEvent extends EventEdit {
constructor(vnode) {
super(vnode);
this.data = {
title_de: 'Unvollstaendiges Event',
priority: 7,
show_website: false,
};
this.valid = false;
// if the creation is finished, UI should switch to new Event
this.callback = (response) => { m.route.set(`/events/${response.data._id}`); };
}
view() {
return m('form', [
this.getForm(),
m(submitButton, {
active: this.valid,
args: {
onclick: this.submit('POST', config.patchableKeys),
class: 'btn-warning',
},
text: 'Create',
}),
]);
}
}
export class EventModal {
constructor() {
this.edit = false;
}
view() {
if (this.edit) {
return m(EventEdit, { onfinish: () => { this.edit = false; m.redraw(); } });
}
// else
return m('div', [
m('div.btn.btn-default', { onclick: () => { this.edit = true; } }, 'Edit'),
m('br'),
m(EventView),
]);
}
}
import m from 'mithril'; import m from 'mithril';
import { RaisedButton, RadioGroup, Switch } from 'polythene-mithril'; import { RaisedButton, RadioGroup, Switch, Dialog, Button } from 'polythene-mithril';
import { fileInput } from 'amiv-web-ui-components'; import { fileInput } from 'amiv-web-ui-components';
import { styler } from 'polythene-core-css'; import { styler } from 'polythene-core-css';
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig'; import { apiUrl, ownUrl } from 'networkConfig';
import EditView from '../views/editView'; import EditView from '../views/editView';
const style = [ const style = [
{ {
'.mywrapper': { '.mywrapper': {
...@@ -20,6 +19,18 @@ export default class newEvent extends EditView { ...@@ -20,6 +19,18 @@ export default class newEvent extends EditView {
constructor(vnode) { constructor(vnode) {
super(vnode); super(vnode);
this.currentpage = 1; this.currentpage = 1;
// check whether the user has the right to create events or can only propose
this.rightSubmit = !m.route.get().startsWith('/proposeevent');
// proposition URL-link decoder
if (this.rightSubmit && m.route.param('proposition')) {
const data = JSON.parse(window.atob(m.route.param('proposition')));
console.log(data);
this.form.data = data;
}
if (!this.form.data.priority) this.form.data.priority = 1; if (!this.form.data.priority) this.form.data.priority = 1;
// read additional_fields to make it editable // read additional_fields to make it editable
...@@ -27,14 +38,14 @@ export default class newEvent extends EditView { ...@@ -27,14 +38,14 @@ export default class newEvent extends EditView {
const copy = JSON.parse(this.form.data.additional_fields); const copy = JSON.parse(this.form.data.additional_fields);
this.form.data.add_fields_sbb = 'SBB_Abo' in copy.properties; this.form.data.add_fields_sbb = 'SBB_Abo' in copy.properties;
this.form.data.add_fields_food = 'Food' in copy.properties; this.form.data.add_fields_food = 'Food' in copy.properties;
this.form.data.additional_fields = {}; this.form.data.additional_fields = null;
} }
// price can either not be set or set to null // price can either not be set or set to null
// if it is 0 however, that would mean that there actually is a price that // if it is 0 however, that would mean that there actually is a price that
// you can edit // you can edit
this.hasprice = 'price' in this.form.data && this.form.data.price !== null; this.hasprice = 'price' in this.form.data && this.form.data.price !== null;
this.hasregistration = 'time_advertising_start' in this.form.data; this.hasregistration = 'spots' in this.form.data || 'time_registration_start' in this.form.data;
} }
beforeSubmit() { beforeSubmit() {
...@@ -95,7 +106,9 @@ export default class newEvent extends EditView { ...@@ -95,7 +106,9 @@ export default class newEvent extends EditView {
delete this.form.data.allow_email_signup; delete this.form.data.allow_email_signup;
} }
console.log(this.form.data); // Propose <=> Submit desicion due to rights
if (this.rightSubmit) {
// Submition tool
if (Object.keys(images).length > 0) { if (Object.keys(images).length > 0) {
images._id = this.form.data._id; images._id = this.form.data._id;
images._etag = this.form.data._etag; images._etag = this.form.data._etag;
...@@ -107,12 +120,50 @@ export default class newEvent extends EditView { ...@@ -107,12 +120,50 @@ export default class newEvent extends EditView {
} else { } else {
this.submit(); this.submit();
} }
} else {
// Propose tool
Dialog.show({
title: 'Congratulations!',
body: [
m(
'div',
'You sucessfuly setup an event.',
'Please send this link to the respectiv board member for validation.',
),
m('input', {
type: 'text',
style: { width: '335px' },
value: `${ownUrl}/newevent?${m.buildQueryString({
proposition: window.btoa(JSON.stringify(this.form.data)),
})}`,
id: 'textId',
}),
],
backdrop: true,
footerButtons: [
m(Button, {
label: 'Copy',
events: {
onclick: () => {
const copyText = document.getElementById('textId');
copyText.select();
document.execCommand('copy');
},
},