Commit fce0a558 authored by lu40's avatar lu40

Merge branch '573-improve-alert-messages' into dev

parents deedbbbb ba457c91
......@@ -97,7 +97,6 @@
"getMember":true,
"pleaseLogin":true,
"ShowServerError": true,
"TemplateMixins": true,
// client/map/map.js
......
import '/imports/startup/both';
import '/imports/startup/client';
import RegionSelection from '/imports/utils/region-selection.js';
import Alert from '/imports/api/alerts/alert.js';
import Languages from '/imports/api/languages/languages.js';
import Introduction from '/imports/ui/lib/introduction.js';
import UpdateViewport from '/imports/ui/lib/update-viewport.js';
import UrlTools from '/imports/utils/url-tools.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import Languages from '/imports/api/languages/languages.js';
import RegionSelection from '/imports/utils/region-selection.js';
import UrlTools from '/imports/utils/url-tools.js';
////////////// db-subscriptions:
......@@ -131,9 +132,12 @@ Accounts.onEmailVerificationLink(function(token, done) {
Router.go('profile');
Accounts.verifyEmail(token, function(error) {
if (error) {
ShowServerError('Address could not be verified', error);
Alert.error(error, 'Address could not be verified');
} else {
AddMessage(mf("email.verified", "Email verified."), 'success');
Alert.success(mf(
'email.verified',
'Your e-mail has been verified.'
));
}
});
});
......
import Alerts from './alerts.js';
export default Alert = {
/** Add an error alert
*
* @param {Error} error - error object
* @param {String} message - the message text
*
*/
error(error, message) {
check(error, Error);
check(message, String);
const errorMessage = mf(
'_serverError',
{ ERROR: error, MESSAGE: errorMessage },
'There was an error on the server: "{MESSAGE} ({ERROR})." Sorry about this.'
);
this._alert('error', errorMessage, 60000);
},
/** Private method to add an alert message
*
* @param {String} type - type of alert message
* @param {String} message - the message text
* @param {Integer} timeout - timeout for the alert to disappear
*
*/
_alert(type, message, timeout = 4000) {
check(type, String);
check(message, String);
check(timeout, Number);
Alerts.insert({ type, message, timeout });
},
};
['success', 'warning'].forEach((type) => {
/** Add an alert of type XY, using the default options
*
* @param {String} message - the message text
*
*/
Alert[type] = function(message) {
check(message, String);
this._alert(type, message);
}
});
import { Mongo } from 'meteor/mongo';
// ======== DB-Model: ========
// "_id" -> ID
// "message" -> {
// "type" -> String
// "message" -> String
// "timeout" -> Integer
// }
export default Alerts = new Mongo.Collection(null);
import { Mongo } from 'meteor/mongo';
export default ClientMessages = new Mongo.Collection(null);
import ClientMessages from './messages.js';
export function AddMessage(message, type = 'info') {
ClientMessages.insert({ message, type });
}
export function RemoveMessage(id) {
ClientMessages.remove({ _id: id });
}
......@@ -4,9 +4,8 @@ import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import CleanedRegion from '/imports/ui/lib/cleaned-region.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import { SetupWarnings, IsEmail } from '/imports/ui/lib/account-tools.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import ScssVars from '/imports/ui/lib/scss-vars.js';
import './account-tasks.html';
......@@ -177,7 +176,7 @@ Template.loginFrame.events({
}, function (err) {
instance.busy(false);
if (err) {
AddMessage(err.reason || 'Unknown error', 'danger');
Alert.error(err, '');
} else {
if (Session.get('viewportWidth') <= ScssVars.gridFloatBreakpoint) {
$('#bs-navbar-collapse-1').collapse('hide');
......@@ -310,9 +309,12 @@ Template.forgotPwdFrame.events({
}, function(err) {
instance.busy(false);
if (err) {
ShowServerError('We were unable to send a mail to this address', err);
Alert.error(err, 'We were unable to send a mail to this address');
} else {
AddMessage(mf('forgot.sent', "we sent a mail with instructions"), 'success');
Alert.success(mf(
'forgotPassword.emailSent',
'An e-mail with further instructions on how to reset your password has been sent to you.'
));
instance.parentInstance().accountTask.set('login');
}
});
......
<template name="messages">
{{#if hasMessages}}
<div class="messages">
{{#each messages}}
{{> message}}
{{/each}}
</div>
{{/if}}
<template name="alerts">
<div class="alert-messages">
{{#each alerts}}
{{> alert}}
{{/each}}
</div>
<div class="alert-messages-spacer"></div>
</template>
<template name="message">
<div class="alert alert-{{type}} alert-dismissible" role="alert">
<template name="alert">
<div class="alert alert-{{contextualClass}} alert-dismissible alert-message" role="alert">
{{message}}
<button type="button" class="close"
<button
aria-label="{{mf 'message.close' 'Close message'}}"
data-dismiss="alert">
class="close js-remove-alert"
type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
......
import { Template } from 'meteor/templating';
import Alerts from '/imports/api/alerts/alerts.js';
import './alerts.html';
Template.alerts.onCreated(function() {
this.updateSpacerHeight = () => {
this.$('.alert-messages-spacer').height(this.$('.alert-messages').height());
};
});
Template.alerts.helpers({
alerts() {
return Alerts.find();
}
});
Template.alert.onCreated(function() {
this.remove = (alertId) => {
const $alert = this.$('.alert-message');
// get 'transition-duration' and convert to miliseconds for fadeOut
const duration = parseFloat($alert.css('transition-duration')) * 1000;
$alert.fadeOut(duration, () => {
this.parentInstance().updateSpacerHeight();
Alerts.remove({ _id: alertId });
});
};
});
Template.alert.onRendered(function() {
this.parentInstance().updateSpacerHeight();
const alert = Template.currentData();
this.$('.alert-message').toggleClass('is-faded-in');
this.timedRemove = setTimeout(() => this.remove(alert._id), alert.timeout);
});
Template.alert.events({
'click .js-remove-alert'(event, instance) {
if (instance.timedRemove) clearTimeout(instance.timedRemove);
instance.remove(this._id);
}
});
Template.alert.helpers({
contextualClass() {
return this.type === 'error' ? 'danger' : this.type;
}
});
.alert-messages {
position: fixed;
left: 0;
top: $navbar-height;
width: 100%;
z-index: $zindex-navbar-fixed - 1;
}
$message-fade-duration: .1s;
.alert-message {
border-radius: 0;
border-top: 0;
opacity: 0;
margin: 0;
transition: opacity $message-fade-duration;
&.is-faded-in {
transition: opacity $message-fade-duration;
opacity: 1;
}
}
.alert-messages-spacer {
height: 0;
transition: height $message-fade-duration;
}
......@@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Template } from 'meteor/templating';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import './delete-events.html';
......@@ -117,14 +117,11 @@ Template.deleteEventsModal.events({
Meteor.call('event.remove', event._id, (err) => {
responses++;
if (err) {
AddMessage(mf(
Alert.error(err, mf(
'deleteEventsModal.errWithReason',
{ REASON: err.reason || 'Unknown error'
, TITLE: event.title
, START: moment(event.startLocal).format('llll')
},
'Deleting the event "{TITLE} ({START})" failed: "{REASON}"'
), 'danger');
{ TITLE: event.title, START: moment(event.startLocal).format('llll') },
'Deleting the event "{TITLE} ({START})" failed.'
));
} else {
removed++;
}
......@@ -133,11 +130,11 @@ Template.deleteEventsModal.events({
instance.busy(false);
instance.state.set('showDeleteConfirm', false);
if (removed) {
AddMessage(mf(
Alert.success(mf(
'deleteEventsModal.sucess',
{ NUM: removed },
'{NUM, plural, one {Event was} other {# events were}} successfully deleted.'
), 'success');
));
}
if (removed === responses) {
instance.state.set('selectedEvents', []);
......
......@@ -4,8 +4,7 @@ import { Template } from 'meteor/templating';
import Courses from '/imports/api/courses/courses.js';
import CourseDiscussions from '/imports/api/course-discussions/course-discussions.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import CourseDiscussionUtils from '/imports/utils/course-discussion-utils.js';
import { HasRoleUser } from '/imports/utils/course-role-utils.js';
import Editable from '/imports/ui/lib/editable.js';
......@@ -313,7 +312,7 @@ Template.post.events({
Meteor.call(method, comment, function(err, commentId) {
instance.busy(false);
if (err) {
ShowServerError('Posting your comment went wrong', err);
Alert.error(err, 'Posting your comment went wrong');
}
});
......@@ -329,9 +328,9 @@ Template.post.events({
event.stopImmediatePropagation();
Meteor.call('courseDiscussion.deleteComment', this._id, function(err) {
if (err) {
ShowServerError('Could not delete comment', err);
Alert.error(err, 'Could not delete comment');
} else {
AddMessage("\u2713 " + mf('_message.removed'), 'success');
Alert.success(mf('discussionPost.deleted', 'Comment has been deleted.'));
}
});
},
......
......@@ -12,8 +12,7 @@ import Roles from '/imports/api/roles/roles.js';
import StringTools from '/imports/utils/string-tools.js';
import Editable from '/imports/ui/lib/editable.js';
import SaveAfterLogin from '/imports/ui/lib/save-after-login.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import { HasRoleUser } from '/imports/utils/course-role-utils.js';
......@@ -337,14 +336,27 @@ Template.courseEdit.events({
Meteor.call('course.save', courseId, changes, (err, courseId) => {
instance.busy(false);
if (err) {
ShowServerError('Saving the course went wrong', err);
Alert.error(err, 'Saving the course went wrong');
} else {
if (instance.data.isFrame) {
instance.savedCourseId.set(courseId);
instance.showSavedMessage.set(true);
instance.resetFields();
} else {
AddMessage("\u2713 " + mf('_message.saved'), 'success');
if (isNew) {
Alert.success(mf(
'message.courseCreated',
{ NAME: changes.name },
'The course "{NAME}" has been created!'
));
} else {
Alert.success(mf(
'message.courseChangesSaved',
{ NAME: changes.name },
'Your changes to the course "{NAME}" have been saved.'
));
}
Router.go('showCourse', { _id: courseId });
}
......
......@@ -5,8 +5,7 @@ import { Template } from 'meteor/templating';
import Roles from '/imports/api/roles/roles.js';
import Editable from '/imports/ui/lib/editable.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import {
HasRoleUser,
MaySubscribe,
......@@ -83,9 +82,9 @@ Template.courseMember.onCreated(function() {
function(newMessage) {
Meteor.call("course.changeComment", courseId, newMessage, function(err, courseId) {
if (err) {
ShowServerError('Unable to change your message', err);
Alert.error(err, 'Unable to change your message');
} else {
AddMessage("\u2713 " + mf('_message.saved'), 'success');
Alert.success(mf('courseMember.messageChanged', 'Your enroll-message has been changed.'));
}
});
},
......
......@@ -3,7 +3,7 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import LocationTracker from '/imports/ui/lib/location-tracker.js';
import Venues from '/imports/api/venues/venues.js';
......@@ -213,14 +213,20 @@ Template.eventEditVenue.events({
params: nominatimQuery
}, function(error, result) {
if (error) {
AddMessage(error);
Alert.error(error, '');
return;
}
var found = JSON.parse(result.content);
markers.remove({ proposed: true });
if (found.length === 0) AddMessage(mf('event.edit.noResultsforAddress', { ADDRESS: search }, 'Found no results for address "{ADDRESS}"'));
if (found.length === 0) {
Alert.warning(mf(
'event.edit.noResultsforAddress',
{ ADDRESS: search },
'Found no results for address "{ADDRESS}"'
));
}
_.each(found, function(foundLocation) {
var marker = {
loc: {"type": "Point", "coordinates":[foundLocation.lon, foundLocation.lat]},
......
......@@ -9,7 +9,6 @@ import { ReactiveDict } from 'meteor/reactive-dict';
import LocalTime from '/imports/utils/local-time.js';
import Editable from '/imports/ui/lib/editable.js';
import SaveAfterLogin from '/imports/ui/lib/save-after-login.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import Regions from '/imports/api/regions/regions.js';
import Courses from '/imports/api/courses/courses.js';
......@@ -21,7 +20,7 @@ import '/imports/ui/components/price-policy/price-policy.js';
import '/imports/ui/components/regions/tag/region-tag.js';
import AffectedReplicaSelectors from '/imports/utils/affected-replica-selectors.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import './event-edit.html';
......@@ -335,7 +334,8 @@ Template.eventEdit.events({
SaveAfterLogin(instance, mf('loginAction.saveEvent', 'Login and save event'), () => {
Meteor.call('event.save',
{
eventId,
eventId: 'xyz',
// eventId,
updateReplicas,
updateChangedReplicas,
sendNotifications,
......@@ -345,17 +345,29 @@ Template.eventEdit.events({
(err, eventId) => {
instance.busy(false);
if (err) {
ShowServerError('Saving the event went wrong', err);
Alert.error(err, 'Saving the event went wrong');
} else {
if (isNew) {
Router.go('showEvent', { _id: eventId });
AddMessage("\u2713 " + mf('_message.saved'), 'success');
Alert.success(mf(
'message.eventCreated',
{ TITLE: editevent.title },
'The event "{TITLE}" has been created!'
));
} else {
AddMessage("\u2713 " + mf('_message.saved'), 'success');
Alert.success(mf(
'message.eventChangesSaved',
{ TITLE: editevent.title },
'Your changes to the event "{TITLE}" have been saved.'
));
}
if (updateReplicas) {
AddMessage(mf('event.edit.replicates.success', { TITLE: editevent.title }, 'Replicas of "{TITLE}" also updated.'), 'success');
Alert.success(mf(
'eventEdit.replicatesUpdated',
{ TITLE: editevent.title },
'The replicas of "{TITLE}" have also been updated.'
));
}
instance.parent.editing.set(false);
}
......
import Events from '/imports/api/events/events.js';
import LocalTime from '/imports/utils/local-time.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import AffectedReplicaSelectors from '/imports/utils/affected-replica-selectors.js';
import '/imports/ui/components/buttons/buttons.js';
......@@ -211,13 +211,11 @@ Template.eventReplication.events({
Meteor.call('event.save', args, (error) => {
responses++;
if (error) {
AddMessage(mf(
Alert.error(error, mf(
'eventReplication.errWithReason',
{ REASON: err.reason || 'Unknown error'
, START: moment(replicaEvent.startLocal).format('llll')
},
'Creating the copy on "{START}" failed: {REASON}'
), 'danger');
{ START: moment(replicaEvent.startLocal).format('llll') },
'Creating the copy on "{START}" failed.'
));
} else {
removed++;
}
......@@ -225,14 +223,14 @@ Template.eventReplication.events({
if (responses === replicaDays.length) {
instance.busy(false);
if (removed) {
AddMessage(mf(
Alert.success(mf(
'event.replicate.successCondensed',
{ TITLE: instance.data.title
, NUM: removed
, DATE: moment(replicaEvent.startLocal).format('llll')
},
'Cloned event "{TITLE}" {NUM, plural, one {for} other {# times until}} {DATE}'
), 'success');
));
}
if (removed === responses) {
const parentInstance = instance.parentInstance();
......
......@@ -5,8 +5,7 @@ import { Template } from 'meteor/templating';
import Groups from '/imports/api/groups/groups.js';
import UserSearchPrefix from '/imports/utils/user-search-prefix.js';
import ShowServerError from '/imports/ui/lib/show-server-error.js';
import { AddMessage } from '/imports/api/messages/methods.js';
import Alert from '/imports/api/alerts/alert.js';
import '/imports/ui/components/buttons/buttons.js';
......@@ -71,9 +70,15 @@ Template.groupSettings.events({
var groupId = Router.current().params._id;
Meteor.call("group.updateMembership", memberId, groupId, true, function(err) {
if (err) {
ShowServerError('Could not add member', err);
Alert.error(err, 'Could not add member');
} else {
AddMessage("\u2713 " + mf('_message.saved'), 'success');
const memberName = Meteor.users.findOne(memberId).username;
const groupName = Groups.findOne(groupId).name;
Alert.success(mf(
'groupSettings.memberAdded',
{ MEMBER: memberName, GROUP: groupName },
'"{MEMBER}" has been added as a member to the group "{GROUP}"'
));
}
});
},
......@@ -83,9 +88,15 @@ Template.groupSettings.events({
var groupId = Router.current().params._id;
Meteor.call("group.updateMembership", memberId, groupId, false, function(err) {
if (err) {
ShowServerError('Could not remove member', err);
Alert.error(err, 'Could not remove member');
} else {
AddMessage("\u2713 " + mf('_message.removed'), 'success');
const memberName = Meteor.users.findOne(memberId).username;
const groupName = Groups.findOne(groupId).name;