Verified Commit 56a7506a authored by Sandro Lutz's avatar Sandro Lutz
Browse files

Styling profile page

parent 39152d24
......@@ -11,6 +11,7 @@ export default {
loadMore: 'Mehr laden',
loadMoreError: 'Laden fehlgeschlagen. Nochmal versuchen?',
retry: 'Erneut versuchen?',
warning: 'Warnung',
switzerland: 'Schweiz',
menu: 'Menu',
amiv: 'AMIV',
......@@ -28,6 +29,7 @@ export default {
confirm: 'bestätigen',
enroll: 'einschreiben',
withdraw: 'ausschreiben',
proceed: 'fortfahren',
},
membership: {
regular: 'ORDENTLICHES MITGLIED',
......@@ -102,7 +104,10 @@ export default {
rfidError: '6 Ziffern erforderlich. Siehe Rückseite deiner Legi.',
password: {
change: 'Passwort ändern',
changed: 'Password wurde geändert.',
revertToLdap: 'Zu LDAP zurückkehren',
revertToLdapWarning:
'Falls du dich nicht über das ETH LDAP authentifizieren kannst, ist das einloggen nicht mehr möglich!\nNur ETH Angehörige sollten diese Aktion ausführen.\n\n**Dies kann nicht rückgängig gemacht werden!**',
set: 'Passwort setzen',
requirements: 'Passwort muss zwischen 7 und 100 Zeichen lang sein.',
current: 'Aktuelles Passwort',
......@@ -111,6 +116,7 @@ export default {
errors: {
current: 'Passwort ist falsch.',
notEqual: 'Passwörter stimmen nicht überein.',
unknown: 'Es ist ein Fehler aufgetreten. Bitte versuche es erneut.',
},
},
newsletter: {
......@@ -123,7 +129,9 @@ export default {
terminateOthers: 'Beende alle anderen {{count}} aktiven Sitzungen.',
},
groups: {
search: 'Gruppen durchsuchen',
searchEnrolled: 'Eingeschriebene Gruppen durchsuchen',
searchPublic: 'Öffentliche Gruppen durchsuchen (mit Selbsteinschreibung)',
noneFound: 'Keine Gruppen gefunden.',
expires: 'läuft am {{date}} ab',
},
},
......
......@@ -11,6 +11,7 @@ export default {
loadMore: 'Load more',
loadMoreError: 'Loading failed. Try again?',
retry: 'Retry',
warning: 'Warning',
switzerland: 'Switzerland',
menu: 'menu',
amiv: 'AMIV',
......@@ -28,6 +29,7 @@ export default {
confirm: 'confirm',
enroll: 'enroll',
withdraw: 'withdraw',
proceed: 'proceed',
},
membership: {
regular: 'REGULAR MEMBER',
......@@ -102,7 +104,10 @@ export default {
rfidError: '6 digits required. See back of your legi.',
password: {
change: 'Change password',
changed: 'Password updated.',
revertToLdap: 'Revert to LDAP',
revertToLdapWarning:
'If you cannot authenticate against the ETH LDAP service, you can no longer log in!\nOnly ETH affiliated people should use this function.\n\n**This cannot be undone!**',
set: 'Set password',
requirements: 'Password has to be between 7 and 100 characters long.',
current: 'Current password',
......@@ -111,6 +116,7 @@ export default {
errors: {
current: 'Password is incorrect.',
notEqual: 'Passwords do not match.',
unknown: 'There was an error. Please try again.',
},
},
newsletter: {
......@@ -123,7 +129,9 @@ export default {
terminateOthers: 'Terminate all other {{count}} active sessions',
},
groups: {
search: 'Search groups',
searchEnrolled: 'Search enrolled groups',
searchPublic: 'Search public groups (with self enrollment)',
noneFound: 'No groups found.',
expires: 'expires on {{date}}',
},
},
......
......@@ -23,6 +23,10 @@
@import './jobofferList.less';
@import './jobofferDetails.less';
* {
box-sizing: border-box;
}
html,body {
width: 100%;
height: 100%;
......@@ -30,7 +34,6 @@ html,body {
padding: 0;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
box-sizing: border-box;
@media @tablet, @mobile {
font-size: 18px;
......
#profile-container {
.profile-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-columns: repeat(6, 1fr);
grid-gap: 1.5em;
border: none;
margin-top: 2em;
margin-bottom: 2em;
}
@media @mobile {
grid-template-columns: repeat(6, 1fr);
}
#info {
grid-column: ~'1 / 13';
grid-row: ~'1 / 2';
background-image: linear-gradient(@color-grey, #fff);
//border: 1px solid @color-grey;
display: flex;
justify-content: space-between;
@media @mobile {
grid-column: ~'1 / 7';
}
}
.profile-container .info-container {
grid-column: ~'1 / 7';
grid-row: ~'1 / 2';
background-image: linear-gradient(@color-grey, #fff);
justify-content: space-between;
#info-user {
grid-column: ~'1 / 7';
.user {
width: 50%;
display: inline-block;
box-sizing: border-box;
text-align: left;
padding: .5em;
margin: .5em;
padding: 1em;
font-size: x-large;
.email {
font-size: .75em;
}
}
#info-amiv {
grid-column: ~'8 / 13';
.amiv {
width: 50%;
display: inline-block;
box-sizing: border-box;
text-align: right;
font-size: large;
padding: 1em;
@media @mobile {
grid-column: ~'1 / 7';
}
}
}
#sessions-subscriptions {
grid-column: ~'1 / 7';
grid-row: ~'2 / 3';
background-color: #fdfdfd;
padding: 1em;
border: 1px solid @color-grey;
.profile-container .groups {
grid-column: ~'4 / 7';
grid-row: ~'2 / 3';
> div {
display: grid;
grid-template-columns: 1fr;
grid-gap: 16px;
}
.announce-subscription {
padding: .1em;
.list {
padding: 0 1em 1em;
max-height: 13em;
overflow-y: auto;
.group-item {
width: 100%;
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-areas: 'text expiry button';
grid-gap: .5em;
align-items: center;
&:first-of-type {
margin-top: 1em;
}
.name {
grid-area: text;
}
.expiry,.confirm {
grid-area: expiry;
}
.enroll,.withdraw,.cancel {
grid-area: button;
}
}
.sessions {
padding: .1em;
.no-items {
text-align: center;
padding: 3em 1em;
}
}
}
#rfid {
grid-column: ~'1 / 7';
grid-row: ~'3 / 4';
background-color: #fdfdfd;
padding: 0 15px 15px;
border: 1px solid @color-grey;
}
.profile-container .settings {
grid-column: ~'1 / 4';
grid-row: ~'2 / 3';
#change-password {
grid-column: ~'1 / 7';
grid-row: ~'4 / 6';
background-color: #fdfdfd;
padding: 0 15px 15px;
border: 1px solid @color-grey;
.newsletter,.sessions {
padding: 1em;
}
#groupmemberships {
grid-column: ~'7 / 13';
grid-row: ~'2 / 4';
background-color: #fdfdfd;
display: flex;
flex-direction: column;
justify-content: flex-start;
grid-template-columns: repeat(3, 1fr);
padding: 15px;
border: 1px solid @color-grey;
max-height: min-content;
.rfid {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: 1em;
padding: 0 1em 1em;
}
@media @mobile {
grid-column: ~'1 / 7';
grid-row: ~'6 / 8';
}
.change-password {
padding: 0 1em 1em;
}
#groups {
grid-column: ~'7 / 13';
grid-row: ~'4 / 6';
background-color: #fdfdfd;
.public-groups {
grid-column: ~'4 / 7';
grid-row: ~'4 / 5';
display: flex;
flex-direction: column;
justify-content: flex-start;
grid-template-columns: repeat(3, 1fr);
padding: 15px;
border: 1px solid @color-grey;
@media @mobile {
grid-column: ~'1 / 7';
grid-row: ~'8 / 10';
}
}
}
#group-search {
grid-row: ~'1 / 2';
grid-column: ~'1 / 4';
margin-top: -10px;
}
#group-list {
grid-row: ~'2 / 3';
grid-column: ~'1 / -1';
max-height: 13em;
overflow: scroll;
.group-entry {
grid-column: ~'1 / -1';
display: grid;
grid-template-columns: repeat(3, 1fr);
align-items: center;
.group-name {
grid-column: ~'1 / 3';
}
.group-expiry {
grid-column: ~'3 / 4';
}
.group-button {
grid-column: ~'4 / 5';
padding: 0;
}
}
}
import m from 'mithril';
import marked from 'marked';
import { apiUrl } from 'config';
import { Dialog, Icon } from 'polythene-mithril';
import { log } from '../../models/log';
import { i18n } from '../../models/language';
import { Button, TextField } from '../../components';
import icons from '../../images/icons';
import { Infobox } from '../errors';
/**
* ChangePasswordForm class
......@@ -10,7 +14,7 @@ import { Button, TextField } from '../../components';
* provides a form to change the users password (or set one if authenticated by LDAP)
*/
export default class ChangePasswordForm {
oninit(vnode) {
constructor(vnode) {
this.userController = vnode.attrs.userController;
this.password_old = '';
this.password1 = '';
......@@ -52,11 +56,13 @@ export default class ChangePasswordForm {
await this.constructor._deleteSession(session);
this.password1 = '';
this.notification = { type: 'success', label: i18n('profile.password.changed') };
} catch ({ _error: { code } }) {
// TODO: show error message
if (code === 401) {
log('Authentication failed.');
this.notification = { type: 'fail', label: i18n('profile.password.errors.current') };
this.valid_old = false;
} else {
this.notification = { type: 'fail', label: i18n('profile.password.errors.unknown') };
log(`An error occurred: ${code}`);
}
}
......@@ -70,9 +76,9 @@ export default class ChangePasswordForm {
// * Minimum length: 7
// * Maximum length: 100
validate() {
this.valid =
this.password_old.length > 0 && this.password1.length >= 7 && this.password1.length <= 100;
this.equal = this.password1 === this.password2;
this.valid_new1 = this.password1.length >= 7 && this.password1.length <= 100;
this.valid_new2 = this.password1 === this.password2;
this.valid = this.valid_old && this.valid_new1 && this.valid_new2;
}
view() {
......@@ -82,7 +88,7 @@ export default class ChangePasswordForm {
const buttonArgs = {};
let buttons;
if (!this.valid || !this.equal || this.busy) {
if (!this.valid || this.busy) {
buttonArgs.disabled = true;
}
......@@ -101,13 +107,46 @@ export default class ChangePasswordForm {
'div',
{ margin: 5 },
m(Button, {
disabled: this.password_old.length === 0,
disabled: !this.valid_old || this.busy,
label: i18n('profile.password.revertToLdap'),
events: {
onclick: () => {
this.password1 = '';
this.password2 = '';
this.submit();
if (!this.valid_old) {
this.validate();
return;
}
Dialog.show({
title: i18n('warning'),
body: m.trust(marked(i18n('profile.password.revertToLdapWarning'))),
modal: true,
backdrop: true,
footerButtons: [
m(Button, {
label: i18n('button.cancel'),
className: 'flat-button',
events: {
onclick: () => {
Dialog.hide();
return false;
},
},
}),
m(Button, {
label: i18n('button.proceed'),
className: 'flat-button',
events: {
onclick: () => {
this.password1 = '';
this.password2 = '';
Dialog.hide();
this.submit();
return false;
},
},
}),
],
});
},
},
})
......@@ -121,17 +160,37 @@ export default class ChangePasswordForm {
});
}
return m('div#change-password', [
let notification;
if (this.notification) {
let iconSource;
if (this.notification.type === 'success') {
iconSource = icons.checkboxMarked;
} else if (this.notification.type === 'fail') {
iconSource = icons.error;
} else {
iconSource = icons.info;
}
notification = m(Infobox, {
icon: m(Icon, { svg: { content: m.trust(iconSource) } }),
label: this.notification.label,
});
}
return m('div.change-password', [
notification,
m(TextField, {
name: 'password_old',
label: i18n('profile.password.current'),
floatingLabel: true,
valid: this.valid,
valid: this.valid_old,
type: 'password',
value: this.password_old,
error: i18n('profile.password.errors.current'),
events: {
oninput: e => {
this.password_old = e.target.value;
this.valid_old = this.password_old.length > 0;
this.validate();
},
},
......@@ -141,7 +200,7 @@ export default class ChangePasswordForm {
label: i18n('profile.password.new'),
floatingLabel: true,
error: i18n('profile.password.requirements'),
valid: this.valid,
valid: this.valid_new1,
type: 'password',
value: this.password1,
events: {
......@@ -155,9 +214,10 @@ export default class ChangePasswordForm {
name: 'password2',
label: i18n('profile.password.repeatNew'),
floatingLabel: true,
valid: this.valid,
valid: this.valid_new2,
type: 'password',
value: this.password2,
error: i18n('profile.password.errors.notEqual'),
events: {
oninput: e => {
this.password2 = e.target.value;
......
import m from 'mithril';
import { Search, Shadow, IconButton } from 'polythene-mithril';
import icons from 'amiv-web-ui-components/src/icons';
import { i18n } from '../../models/language';
import { Button, TextField } from '../../components';
import { Button } from '../../components';
// shows group memberships and allows to withdraw their memberships.
export default class GroupMemberships {
oninit(vnode) {
this.groupMembershipsController = vnode.attrs.groupMembershipsController;
class SearchableList {
constructor() {
this.busy = [];
this.confirm = [];
this.query = '';
this.searchLabel = 'search';
}
// eslint-disable-next-line class-methods-use-this
notify() {
// Should be implemented in child class if the filtering is done on the server.
}
renderMembership(membership) {
view() {
const clearButton = m(IconButton, {
icon: { svg: { content: m.trust(icons.clear) } },
ink: false,
events: {
onclick: () => {
this.query = '';
this.notify();
},
},
});
return [
m(Search, {
textfield: {
label: this.searchLabel,
value: this.query,
onChange: state => {
this.query = state.value;
this.notify();
},
},
after: m(Shadow),
fullWidth: true,
buttons: {
dirty: {
after: clearButton,
},
focus_dirty: {
after: clearButton,
},
},
}),
m('div.list', this._renderItems()),
];
}
}
// shows public groups and allows to enroll for them.
export class PublicGroups extends SearchableList {
constructor(vnode) {
super(vnode);
this.groupMembershipsController = vnode.attrs.groupMembershipsController;
this.publicGroupsController = vnode.attrs.publicGroupsController;
this.searchLabel = i18n('profile.groups.searchPublic');
}
notify() {
if (
this.publicGroupsController.setQuery({
where: { name: { $regex: `^(?i).*${this.query}.*` } },
})
) {
this.publicGroupsController.loadPageData(1);
}
}
_renderItems() {
let itemsCount = 0;
const items = this.publicGroupsController.map(page =>
page.map(group => {
const item = this._renderGroup(group);
if (item) itemsCount += 1;
return item;
})
);
if (itemsCount === 0) {
return m('div.no-items', i18n('profile.groups.noneFound'));
}
return items;
}
_renderGroup(group) {
if (this.groupMembershipsController.some(element => element.group._id === group._id)) {
return undefined;
}
const buttonArgs = {
events: {
onclick: () => {
this.busy[group._id] = true;
this.groupMembershipsController
.enroll(group._id)
.then(() => {
this.busy[group._id] = false;
this.groupMembershipsController.loadAll();
})
.catch(() => {
this.busy[group._id] = false;
});
},
},
};
if (this.busy[group._id]) buttonArgs.disabled = true;
this.itemsCount += 1;
return m('div.group-item', [