Commit 4b6c554c authored by fanconic's avatar fanconic
Browse files

Merge branch 'master' into frontpage

parents 0def521e c33ef850
......@@ -3,3 +3,5 @@ package-lock.json
dist
.vscode
npm-debug.log
DOCUMENTATION.html
DOCUMENTATION.md
\ No newline at end of file
......@@ -4,32 +4,44 @@ This is the home of the new AMIV website.
## How to use
**Developer**
```
### Development
```bash
npm install
npm run server #start developer server
```
**Production**
```
### Production
```bash
npm install
npm run build
```
Then copy `index.html` and the dist folder to your webhost.
**Lint**
```
### Lint
```bash
npm run lint
```
## Developer
### Documentation
```bash
npm run docs # output format: HTML
npm run docs-md # output format: markdown
```
## Developer
Backend: [AMIV API](https://github.com/amiv-eth/amivapi)
Technologies:
* [Mitrhil](https://mithril.js.org/)
* [Mitrhil](https://mithril.js.org/)
Needed extensions:
* [ESlint](https://github.com/eslint/eslint)
* [Prettier](https://github.com/prettier/prettier)
\ No newline at end of file
* [ESlint](https://github.com/eslint/eslint)
* [Prettier](https://github.com/prettier/prettier)
\ No newline at end of file
......@@ -5,6 +5,8 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs": "doxdox 'src/**/*.js' --layout bootstrap --output DOCUMENTATION.html",
"docs-md": "doxdox 'src/**/*.js' --layout markdown --output DOCUMENTATION.md",
"start": "webpack -d --watch",
"build": "webpack -p --config webpack.config.prod.js",
"server": "webpack-dev-server --hot --inline",
......@@ -33,7 +35,9 @@
"polythene-mithril": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.0.1",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.3"
"webpack-dev-server": "^2.9.3",
"dox": "^0.9.0",
"doxdox": "^2.0.3"
},
"devDependencies": {
"babel-plugin-transform-object-rest-spread": "^6.26.0",
......
......@@ -7,6 +7,24 @@ ButtonCSS.addStyle('.blue-button', {
color_light_text: 'white',
});
/**
* Generic button component
*
* Attributes:
*
* - `className` defaulting to `blue-button`
* - `element` HTML tag; defaulting to `button`
* - `active` enables/disables the component; defaulting to `true`
* - `label` text shown on the button; defaulting to `Unnamed button`
* - `onclick` *optional*
*
* Examples:
*
* m(ButtonComponent, { label: 'submit' })
* m(ButtonComponent, { className: 'anotherclass', label: 'submit' })
*
* @return {ButtonComponent} generic button as mithril component.
*/
export default class ButtonComponent {
constructor(vnode) {
this.defaultProps = {
......@@ -17,6 +35,10 @@ export default class ButtonComponent {
};
}
onbeforeupdate(vnode) {
this.defaultProps.disabled = vnode.attrs.active === false;
}
view(vnode) {
return m(Button, { ...this.defaultProps, ...vnode.attrs });
}
......
import m from 'mithril';
import { Tabs } from 'polythene-mithril';
import { TabsCSS } from 'polythene-css';
TabsCSS.addStyle('.themed-tabs', {
tab_max_width: 110,
tab_min_width: 110,
color_light: '#444',
color_light_selected: '#ff1744',
color_light_tab_indicator: '#ff1744',
color_dark: '#ccc',
color_dark_selected: '#c51162',
color_dark_tab_indicator: '#c51162',
});
export default class TabComponent {
constructor() {
this.defaultProps = {
className: 'themed-tabs',
activeSelected: true,
};
}
view(vnode) {
return m(Tabs, { ...this.defaultProps, ...vnode.attrs });
}
}
export { default as Button } from './Button';
export { default as Tabs } from './Tabs';
......@@ -12,14 +12,29 @@ const APISession = {
lastChecked: 0,
};
/**
* Get the `userId` of the authenticated user.
*
* @return {String} user id
*/
export function getUserId() {
return APISession.userId;
}
/**
* Get the `username` of the authenticated user.
*
* @return {String} username
*/
export function getUsername() {
return APISession.username;
}
/**
* Get the `api token` of the authenticated user.
*
* @return {String} api token
*/
export function getToken() {
return APISession.token;
}
......@@ -36,6 +51,13 @@ function reloadLocalStorage() {
}
}
/**
* Authenticate a user.
*
* @param {String} username user to be authenticated
* @param {String} password password to be used
* @return {Promise} exports for additional response handling
*/
export function login(username, password) {
reloadLocalStorage();
return m
......@@ -63,6 +85,11 @@ export function login(username, password) {
});
}
/**
* Revoke current authentication.
*
* @return {Promise} exports for additional response handling
*/
export function logout() {
reloadLocalStorage();
APISession.authenticated = false;
......@@ -92,6 +119,11 @@ export function logout() {
});
}
/**
* Check if the stored authentication data is still valid.
*
* @return {Promise} exports for additional response handling
*/
export function checkLogin() {
const dt = new Date();
reloadLocalStorage();
......@@ -127,6 +159,10 @@ export function checkLogin() {
return new Promise(() => {});
}
/**
* Check if authentication data is available.
* @return {Boolean} `true` if authentication data is available
*/
export function isLoggedIn() {
return APISession.authenticated;
}
......@@ -3,8 +3,14 @@ import m from 'mithril';
// ensure that all markdown files are compiled
require.context('../views/companies/markdown');
/**
* Load company profile asynchronously from the compiled html files.
*
* @param {String} companyId company to load. This corresponds to the name of the markdown file.
* @return {Promise} exports for additional response handling
*/
export default function load(companyId) {
// dynamically load markdown
// dynamically load compiled html files
return m.request({
url: `/dist/companies/${companyId}.html`,
method: 'GET',
......
/**
* Url used for calls to the AMIV API
*/
export const apiUrl = 'https://amiv-api.ethz.ch';
/**
* Enable/Disable verbose output.
*/
export const verbose = true;
......@@ -7,6 +7,11 @@ const date = `${new Date().toISOString().split('.')[0]}Z`;
let querySaved = '';
/**
* Get the loaded list of events.
*
* @return {array}
*/
export function getList() {
if (typeof this.list === 'undefined') {
return [];
......@@ -14,18 +19,37 @@ export function getList() {
return this.list;
}
/**
* Get the selected event.
*
* @return {Object} `event` object returned by the AMIV API.
*/
export function getSelectedEvent() {
return this.selectedEvent;
}
/**
* Get signup data for the selected event and the authenticated user.
*
* @return {Object} `eventsignup` object returned by the AMIV API.
*/
export function getSignupForSelectedEvent() {
return this.selectedEventSignup;
}
/**
* Check if signup data of the authenticated user for the selected event have been loaded.
*
* @return {Boolean}
*/
export function signupForSelectedEventHasLoaded() {
return this.selectedEventSignupLoaded;
}
/**
* Load signup data of the authenticated user for the selected event.
* @return {Promise} exports for additional response handling
*/
export function loadSignupForSelectedEvent() {
const queryString = m.buildQueryString({
where: JSON.stringify({
......@@ -112,6 +136,11 @@ export function _signupEmailForSelectedEvent(additionalFieldsString, email) {
});
}
/**
* Sign up the authenticated user for the selected event.
*
* @return {Promise} exports for additional response handling
*/
export function signupForSelectedEvent(additionalFields, email = '') {
let additionalFieldsString;
if (
......@@ -132,6 +161,11 @@ export function signupForSelectedEvent(additionalFields, email = '') {
return Promise.reject(new Error('Signup not allowed'));
}
/**
* Sign off the authenticated user from the selected event.
*
* @return {Promise} exports for additional response handling
*/
export function signoffForSelectedEvent() {
if (isLoggedIn() && typeof this.selectedEventSignup !== 'undefined') {
m
......@@ -151,6 +185,12 @@ export function signoffForSelectedEvent() {
}
}
/**
* Load events from the AMIV API
*
* @param {*} query filter and sort query for the API request.
* @return {Promise} exports for additional response handling
*/
export function load(query = {}) {
querySaved = query;
......@@ -181,6 +221,11 @@ export function load(query = {}) {
});
}
/**
* Select an event from the event list.
*
* @param {String} eventId event id from AMIV API
*/
export function selectEvent(eventId) {
this.selectedEvent = this.getList().find(item => item._id === eventId);
if (typeof this.selectedEvent === 'undefined') {
......@@ -197,6 +242,11 @@ export function selectEvent(eventId) {
}
}
/**
* Reload event list with the same query as before.
*
* @return {Promise} exports for additional response handling
*/
export function reload() {
return load(querySaved);
}
......@@ -5,6 +5,11 @@ import { error } from './log';
let querySaved = '';
/**
* Get the loaded list of groups.
*
* @return {array} `group` objects returned by the AMIV API.
*/
export function getList() {
if (this.groups === undefined) {
return [];
......@@ -12,6 +17,11 @@ export function getList() {
return this.groups;
}
/**
* Get the memberships for the authenticated user.
*
* @return {array} `groupmembership` objects with embedded groups returned by the AMIV API.
*/
export function getMemberships() {
if (this.memberships === undefined) {
return [];
......@@ -19,6 +29,12 @@ export function getMemberships() {
return this.memberships;
}
/**
* Enroll the authenticated user to a group
*
* @param {String} groupId
* @return {Promise} exports for additional response handling
*/
export function enroll(groupId) {
return m
.request({
......@@ -46,6 +62,13 @@ export function enroll(groupId) {
});
}
/**
* Withdraw membership of the authenticated user from a group.
*
* @param {String} groupMembershipId groupmembership id
* @param {String} etag value given by AMIV API to be used as `If-Match` header.
* @return {Promise} exports for additional response handling
*/
export function withdraw(groupMembershipId, etag) {
return m
.request({
......@@ -66,6 +89,12 @@ export function withdraw(groupMembershipId, etag) {
});
}
/**
* Load groups from the AMIV API.
*
* @param {*} query filter and sort query for the API request.
* @return {Promise} exports for additional response handling
*/
export function load(query = {}) {
const queryEncoded = m.buildQueryString({ where: JSON.stringify(query) });
querySaved = query;
......@@ -88,6 +117,11 @@ export function load(query = {}) {
});
}
/**
* Load groupmemberships of the authenticated user from the AMIV API.
*
* @return {Promise} exports for additional response handling
*/
export function loadMemberships() {
const queryEncoded = m.buildQueryString({
where: JSON.stringify({ user: getUserId() }),
......@@ -111,6 +145,11 @@ export function loadMemberships() {
});
}
/**
* Reload event list with the same query as before.
*
* @return {Promise} exports for additional response handling
*/
export function reload() {
return load(querySaved);
}
......@@ -7,6 +7,11 @@ const date = `${new Date().toISOString().split('.')[0]}Z`;
let querySaved = {};
/**
* Get the loaded list of joboffers.
*
* @return {array}
*/
export function getList() {
if (!this.list) {
return [];
......@@ -14,10 +19,21 @@ export function getList() {
return this.list;
}
/**
* Get the selected joboffer.
*
* @return {Object} `joboffer` object returned by the AMIV API.
*/
export function getSelectedOffer() {
return this.selectedOffer;
}
/**
* Load joboffers from the AMIV API
*
* @param {*} query filter and sort query for the API request.
* @return {Promise} exports for additional response handling
*/
export function load(query = {}) {
querySaved = query;
......@@ -46,6 +62,11 @@ export function load(query = {}) {
});
}
/**
* Select a joboffer from the list.
*
* @param {String} offerId joboffer id from AMIV API
*/
export function selectOffer(offerId) {
this.selectedOffer = this.getList().find(item => item._id === offerId);
if (!this.selectedOffer) {
......@@ -61,6 +82,11 @@ export function selectOffer(offerId) {
}
}
/**
* Reload joboffer list with the same query as before.
*
* @return {Promise} exports for additional response handling
*/
export function reload() {
return load(querySaved);
}
import { verbose } from './config';
/**
* Print `log message` to the console if verbose is enabled.
*
* @param {*} message log message to be printed to the console.
*/
export function log(message) {
if (verbose === true) console.log(message);
}
/**
* Print `error message` to the console if verbose is enabled.
*
* @param {*} message error message to be printed to the console.
*/
export function error(message) {
if (verbose === true) console.error(message);
}
......@@ -4,6 +4,11 @@ import { getToken } from './auth';
let querySaved = {};
/**
* Get the loaded list of studydocuments.
*
* @return {array}
*/
export function getList() {
if (typeof this.list === 'undefined') {
return [];
......@@ -11,6 +16,12 @@ export function getList() {
return this.list;
}
/**
* Load studydocuments from the AMIV API
*
* @param {*} query filter and sort query for the API request.
* @return {Promise} exports for additional response handling
*/
export function load(query = {}) {
querySaved = query;
const queryEncoded = m.buildQueryString({ where: JSON.stringify(query) });
......@@ -28,6 +39,12 @@ export function load(query = {}) {
});
}
/**
* Get Suggestions from already existing entries for a specified field of `studydocument`.
*
* @param {String} field entity field which should be searched.
* @param {String} input search string
*/
export function getInputSuggestions(field, input) {
const query = {};
query[field] = { $regex: `^(?i).*${input}.*` };
......@@ -47,10 +64,21 @@ export function getInputSuggestions(field, input) {
});
}
/**
* Reload studydocument list with the same query as before.
*
* @return {Promise} exports for additional response handling
*/
export function reload() {
return load(querySaved);
}
/**
* Store a new studydocument in the AMIV API.
*
* @param {Object} doc studydocument object to be stored in the AMIV API.
* @return {Promise} exports for additional response handling
*/
export function addNew(doc) {
if (typeof doc !== 'object') {
return new Promise(() => {}); // empty promise
......
......@@ -3,7 +3,12 @@ import { apiUrl } from './config';
import { getToken, getUserId } from './auth';
import { log, error } from './log';
// update data of logged in user
/**
* Update data of the authenticated user.
*
* @param {Object} options any subset of user properties.
* @return {Promise} exports for additional response handling
*/
export function update(options) {
log(this.user._etag);
......@@ -27,7 +32,11 @@ export function update(options) {
});
}
// get available data of logged in user
/**
* Get available user data of the authenticated user.
*
* @return {Object} `user` object returned by the AMIV API.
*/
export function get() {
if (typeof this.user === 'undefined') {
return {};
......@@ -36,6 +45,11 @@ export function get() {
}
// load information of logged in user
/**
* Load user data of the authenticated user from the AMIV API.
*
* @return {Promise} exports for additional response handling
*/
export function load() {
return m
.request({
......
......@@ -138,7 +138,7 @@ export default class EventDetails {
} else {
let participantNotice = '';
if (events.getSignupForSelectedEvent() !== 'undefined') {
participantNotice = m('You signed up for this event.');
participantNotice = m('span', 'You signed up for this event.');
}
eventSignupForm = m('div', ['The registration period is over.', participantNotice]);
}
......
......@@ -78,7 +78,6 @@ export default class Frontpage {
onbeforeupdate() {
this.events = events.getList().slice(0, 3);
console.log(this.events);
}