Commit 4e80519a authored by Sandro Lutz's avatar Sandro Lutz Committed by lic
Browse files

Events page

parent c5bf3353
......@@ -102,7 +102,6 @@ import './FilterView.less';
export default class FilterViewComponent {
oninit({ attrs: { values, fields, delay = 500, onchange } }) {
this.onchange = debounce(onchange, delay, false);
// this.onchange = onchange;
if (values && Object.keys(values).length >= 0) {
this.values = values;
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" d="M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z" />
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" d="M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20Z" />
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path fill="#000000" d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" />
</svg>
......@@ -121,21 +121,19 @@
"events.header_upcoming": "Bevorstehende Events",
"events.header_past": "Vergangene Events",
"events.no_upcoming": "Keine bevorstehenden Events.",
"events.searchfield": "Suchtext eingeben...",
"events.price": "Preis",
"events.free": "Gratis",
"events.small_fee": "Kleine Teilnahmegebühr",
"events.all_events": "Alle Events",
"events.restrictions": "Einschränkungen",
"events.open_for_all": "Offen für alle",
"events.open_for_amiv_members_only": "Nur für AMIV Mitglieder",
"events.not_found": "Event nicht gefunden",
"events.no_selection": "Kein Event ausgewählt",
"events.signed_up": "Du hast dich für diesen Event angemeldet.",
"events.no_registration": "Keine Anmeldung erforderlich",
"events.registration_over": "Das Anmeldefenster ist geschlossen.",
"events.registration_starts_at": "Das Anmeldefenster öffnet am %{time}",
"events.registration_starts_at": "Anmeldung möglich ab",
"events.location": "Ort",
"events.%n_spots_available": [
[-1, -1, "Plätze verfügbar"],
[0, 0, "Keine Plätze verfügbar"],
[1, 1, "%n Platz verfügbar"],
[2, null, "%n Plätze verfügbar"]
......@@ -143,11 +141,17 @@
"events.update_data": "Alktualisiere deine Daten im unteren Formular.",
"events.amiv_members_only": "Dieser Event ist nur für AMIV Mitglieder.",
"events.signup": "anmelden",
"events.delete_signup": "Anmeldung löschen",
"events.update_signup": "Anmeldung ändern",
"events.delete_signup": "abmelden",
"events.loading": "Laden...",
"events.load_more": "Mehr Events laden",
"events.emailsignup_success": "Anmeldung erfolgreich.",
"events.emailsignup_fail": "Anmeldung fehlgeschlagen. Probier es später wieder.",
"events.signup_success": "Du hast einen Platz ergattert!",
"events.signup_waiting_list": "Du bist auf der Warteliste.",
"events.signup_fail": "Anmeldung fehlgeschlagen",
"events.signoff_success": "Anmeldung gelöscht",
"events.signoff_fail": "Abmelden fehlgeschlagen",
"events.signed_up_accepted": "Du bist angemeldet und hast einen Platz.",
"events.signed_up_waiting_list": "Du bist auf der Warteliste.",
"companies.contact_information": "Kontakt-Informationen",
"companies.email": "Email",
"companies.phone": "Telefon",
......@@ -163,8 +167,34 @@
"errors.not_found.text": "Die gewünschte Seite konnte nicht gefunden werden.",
"errors.translation_unavailable": "Übersetzung nicht verfügbar.",
"errors.shown_language": "Zeige Text in %{shown_language}",
"errors.retry": "Wiederholen",
"filtered_list.show_filter": "Filter anzeigen",
"filtered_list.hide_filter": "Filter verstecken",
"filtered_list.show_list": "Zurück zur Übersicht"
"filtered_list.show_list": "Zurück zur Übersicht",
"date.at": "um",
"date.oclock": "Uhr",
"date.weekdays%n":[
[0, 0, "Sonntag"],
[1, 1, "Montag"],
[2, 2, "Dienstag"],
[3, 3, "Mittwoch"],
[4, 4, "Donnerstag"],
[5, 5, "Freitag"],
[6, 6, "Samstag"]
],
"date.months%n":[
[0, 0, "Januar"],
[1, 1, "Februar"],
[2, 2, "März"],
[3, 3, "April"],
[4, 4, "Mai"],
[5, 5, "Juni"],
[6, 6, "Juli"],
[7, 7, "August"],
[8, 8, "September"],
[9, 9, "Oktober"],
[10, 10, "November"],
[11, 11, "Dezember"]
]
}
}
......@@ -121,21 +121,19 @@
"events.header_upcoming": "Upcoming Events",
"events.header_past": "Past Events",
"events.no_upcoming": "No upcoming events.",
"events.searchfield": "Enter search text...",
"events.price": "Price",
"events.free": "Free",
"events.small_fee": "Small fee",
"events.all_events": "All Events",
"events.restrictions": "Restrictions",
"events.open_for_all": "Open for everyone",
"events.open_for_amiv_members_only": "For AMIV members only",
"events.not_found": "Event not found",
"events.no_selection": "No event selected",
"events.signed_up": "You signed up for this event.",
"events.no_registration": "No registration required",
"events.registration_over": "The registration period is over.",
"events.registration_starts_at": "The registration starts at %{time}",
"events.registration_starts_at": "Registration starts at",
"events.location": "Location",
"events.%n_spots_available": [
[-1, -1, "Spots available"],
[0, 0, "No spots available"],
[1, 1, "%n spot available"],
[2, null, "%n spots available"]
......@@ -143,9 +141,17 @@
"events.update_data": "Update your data below.",
"events.amiv_members_only": "This event is for AMIV members only.",
"events.signup": "signup",
"events.delete_signup": "delete signup",
"events.update_signup": "change signup",
"events.delete_signup": "signoff",
"events.loading": "Loading...",
"events.load_more": "Load more events",
"events.signup_success": "Success! You're in.",
"events.signup_waiting_list": "You are on the waiting list.",
"events.signup_fail": "Signup failed",
"events.signoff_success": "Signoff successful",
"events.signoff_fail": "Signoff failed",
"events.signed_up_accepted": "You are signed up and got a spot.",
"events.signed_up_waiting_list": "You are on the waiting list.",
"companies.contact_information": "Contact information",
"companies.email": "Email",
"companies.phone": "Phone",
......@@ -161,10 +167,34 @@
"errors.not_found.text": "The page you are looking for could not be found.",
"errors.translation_unavailable": "Translation not available.",
"errors.shown_language": "Showing text in %{shown_language}",
"events.emailsignup_success": "Sign up successful.",
"events.emailsignup_fail": "Sign up failed. Try again later.",
"errors.retry": "Retry",
"filtered_list.show_filter": "Show filter",
"filtered_list.hide_filter": "Hide filter",
"filtered_list.show_list": "Back to the list"
"filtered_list.show_list": "Back to the list",
"date.at": "at",
"date.oclock": "",
"date.weekdays%n":[
[0, 0, "Sunday"],
[1, 1, "Monday"],
[2, 2, "Tuesday"],
[3, 3, "Wednesday"],
[4, 4, "Thursday"],
[5, 5, "Friday"],
[6, 6, "Saturday"]
],
"date.months%n":[
[0, 0, "January"],
[1, 1, "February"],
[2, 2, "March"],
[3, 3, "April"],
[4, 4, "May"],
[5, 5, "June"],
[6, 6, "July"],
[7, 7, "August"],
[8, 8, "September"],
[9, 9, "October"],
[10, 10, "November"],
[11, 11, "December"]
]
}
}
......@@ -41,7 +41,7 @@ export default class Event {
*
* @return {Promise}
*/
async loadSignup() {
async loadSignupData() {
if (!isLoggedIn()) return undefined;
const queryString = m.buildQueryString({
......@@ -102,7 +102,7 @@ export default class Event {
},
});
this._signup = undefined;
this.signupLoaded = false;
this.signupLoaded = true;
}
/**
......
......@@ -79,4 +79,23 @@ function currentLanguage() {
return _currentLanguage;
}
export { i18n, changeLanguage, setLanguage, currentLanguage, loadLanguage, isLanguageValid };
/**
* Get the current locale based on the configured language.
*
* @return locale string
*/
function currentLocale() {
if (_currentLanguage === 'en') return 'en-GB';
return 'de-DE';
}
export {
i18n,
changeLanguage,
setLanguage,
currentLanguage,
currentLocale,
loadLanguage,
isLanguageValid,
};
......@@ -4,6 +4,7 @@
@import './teams.less';
@import './dimensions.less';
@import './errors.less';
@import './eventList.less';
@import './eventDetails.less';
@import './filteredListPage.less';
@import './footer.less';
......
@import './colors.less';
.event-details {
display: grid;
grid-template-areas: 'description separator signup';
grid-template-columns: 1fr 4px 350px;
border-top: 1px solid @color-grey;
align-items: center;
min-height: 10em;
@media @tablet, @mobile {
grid-template: auto 4px auto / 1fr;
grid-template-areas:
'description'
'separator'
'signup';
min-height: unset;
}
> p {
grid-area: description;
margin: 1em;
}
.separator {
grid-area: separator;
background: @color-grey;
width: 100%;
height: calc(100% - 2em);
margin: 1em 0;
@media @tablet, @mobile {
width: calc(100% - 2em);
height: 100%;
margin: 0 1em;
}
}
.form {
grid-area: signup;
position: relative;
margin: 2em 1em;
text-align: center;
> p {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
}
.colored {
color: @color-red;
font-size: 1.5em;
}
form div:not(.pe-button__content) {
width: 100%;
}
}
button {
margin-right: 1em;
}
......
.event {
display: block;
@import './colors.less';
.event-header {
display: grid;
grid-template-areas: 'image content';
grid-template-columns: 150px 1fr;
@media @mobile {
grid-template-areas: 'content';
grid-template-columns: 1fr;
}
.image {
grid-area: image;
display: block;
@media @mobile {
display: none;
}
}
.content {
grid-area: content;
padding: 1em;
.title {
padding: 0;
padding-bottom: .25em;
margin: 0;
}
.catchphrase {
font-style: italic;
padding-bottom: 1em;
}
.date {
padding-bottom: .75em;
}
.properties {
width: 100%;
}
.property {
display: inline-block;
&:after {
content: '/';
color: @color-red;
margin: 0 1em;
}
.name:after {
content: ':';
padding-right: .5em;
}
&:last-of-type:after {
content: '';
margin: 0;
}
}
}
}
......@@ -92,7 +92,6 @@
@media @mobile {
border: none;
padding: 1em;
min-height: 50vh;
}
.loading {
......
......@@ -7,6 +7,7 @@
padding: .5em .5em .5em 2.5em;
background-color: @color-grey;
line-height: 1.5em;
border-radius: 4px;
img {
position: absolute;
......
......@@ -73,3 +73,17 @@ export class TranslationUnavailable {
);
}
}
/**
* View to show some generic infobox.
*
* @param {string} icon SVG icon
* @param {string} label Text label
*
* @return {Infobox}
*/
export class Infobox {
static view({ attrs: { icon, label } }) {
return m('div', { class: 'infobox' }, [m('img', { src: icon }), m('span', label)]);
}
}
import m from 'mithril';
import marked from 'marked';
import escape from 'html-escape';
import { TextField } from 'polythene-mithril';
import { Form } from 'amiv-web-ui-components';
import { Form, TextInput, Spinner } from 'amiv-web-ui-components';
import { Infobox } from '../errors';
import { log } from '../../models/log';
import { isLoggedIn, login } from '../../models/auth';
import { Button } from '../../components';
import { i18n } from '../../models/language';
import { i18n, currentLocale } from '../../models/language';
import infoIcon from '../../images/info.svg';
import errorIcon from '../../images/error.svg';
import successIcon from '../../images/checkbox-marked.svg';
class EventSignupForm {
export default class EventDetails {
oninit(vnode) {
this.signupFetchError = false;
this.event = vnode.attrs.event;
this.schema =
this.event.additional_fields === undefined
? undefined
: JSON.parse(this.event.additional_fields);
this.form = new Form();
if (this.schema && this.schema.$schema) {
// ajv fails to verify the v4 schema of some resources
this.schema.$schema = 'http://json-schema.org/draft-06/schema#';
this.form.setSchema(this.schema);
}
this.notification = null;
this.signupBusy = false;
this.signoffBusy = false;
this.schema = !this.event.additional_fields
? undefined
: JSON.parse(this.event.additional_fields);
this.email = '';
this.emailSignup = null;
if (isLoggedIn()) {
this.event.loadSignup().then(() => {
this._createForm();
if (isLoggedIn() && !this.event.hasSignupDataLoaded) {
this._loadSignupData();
}
}
_loadSignupData() {
this.event
.loadSignupData()
.then(() => {
this.signupFetchError = false;
if (this.event.signupData) {
this.form.data = JSON.parse(this.event.signupData.additional_fields || '{}');
this._createForm();
}
})
.catch(() => {
this.signupFetchError = true;
});
}
_createForm() {
if (this.event.signupData && this.event.signupData.additional_fields) {
this.form = new Form(
null,
false,
4,
JSON.parse(this.event.signupData.additional_fields || '{}')
);
} else {
this.form = new Form();
}
if (this.schema && this.schema.$schema) {
// ajv fails to verify the v4 schema of some resources
this.schema.$schema = 'http://json-schema.org/draft-06/schema#';
this.form.setSchema(this.schema);
}
}
async signup() {
try {
await this.event.signup(super.getValue(), this.email);
this.emailSignup = 'success';
this.signupBusy = true;
await this.event.signup(this.form.getData(), this.email);
if (this.event.signupData.accepted) {
this.notification = { type: 'success', label: i18n('events.signup_success') };
} else {
this.notification = { type: 'success', label: i18n('events.signup_waiting_list') };
}
} catch (err) {
log(err);
this.emailSignup = 'fail';
this.notification = { type: 'fail', label: i18n('events.signup_fail') };
}
this.signupBusy = false;
}
signoff() {
async signoff() {
try {
this.event.signoff();
this.signoffBusy = true;
await this.event.signoff();
this._createForm();
this.notification = { type: 'success', label: i18n('events.signoff_success') };
} catch (err) {
log(err);
this.notification = { type: 'fail', label: i18n('events.signoff_fail') };
}
this.form.validate();
this.signoffBusy = false;
}
view() {
if (isLoggedIn()) {
// do not render form if there is no signup data of the current user
if (!this.event.hasSignupDataLoaded) return m('span', i18n('loading'));
let eventSignupForm;
const now = new Date();
const registerStart = new Date(this.event.time_register_start);
const registerEnd = new Date(this.event.time_register_end);
const elements = this.schema ? this.form.renderSchema() : [];
if (!this.event.signupData || (this.event.signupData && this.event.additional_fields)) {
elements.push(this._renderSignupButton());
}
if (this.event.signupData) {
elements.unshift(
m(
'div',
`${i18n('events.signed_up')} ${
this.event.additional_fields ? i18n('events.update_data') : ''
}`
)
);
elements.push(this._renderSignoffButton());
if (registerStart <= now) {
if (registerEnd >= now) {
if (isLoggedIn()) {
if (!this.event.hasSignupDataLoaded) {
if (this.signupFetchError) {
eventSignupForm = m('div', [
m('p', i18n('errors.title')),
m(
'a.colored',
{
href: '#',
onclick: this._loadSignupData,
},
`${i18n('errors.retry')}?`
),
]);
} else {
eventSignupForm = m(Spinner, { show: true });
}
} else {
const signupFormOptions = {
signoffButton: this.event.signupData != null,
hasSignupData: this.event.signupData != null,
};
eventSignupForm = this._renderSignupForm(signupFormOptions);
}
} else if (this.event.allow_email_signup) {
const signupFormOptions = {
emailField: true,
};
eventSignupForm = this._renderSignupForm(signupFormOptions);
} else {
eventSignupForm = m('div', [
m('span', `${i18n('events.amiv_members_only')} `),
m(Button, { label: i18n('Login'), events: { onclick: () => login(m.route.get()) } }),
]);
}
this._renderParticipationNotice();
} else {
eventSignupForm = m('div', m('p', i18n('events.registration_over')));
this._renderParticipationNotice();
}
return m('form', { onsubmit: () => false }, elements);
} else if (this.event.allow_email_signup) {
if (!this.emailSignup) {
const elements = this.schema ? this.form.renderSchema() : [];
elements.push(this._renderEmailField());
elements.push(this._renderSignupButton());
return m('form', { onsubmit: () => false },