const $ = require('jquery'); const moment = require('moment'); const CoreView = require('backbone/core-view'); const UserNotificationsCollection = require('./notifications-collection'); const NotificationsDropdown = require('./dropdown-view'); const template = require('./templates/user-notifications.tpl'); const dropdownTemplate = require('./templates/dropdown.tpl'); const LocalStorage = require('../../../helpers/local-storage'); const checkAndBuildOpts = require('../../../../builder/helpers/required-opts'); const TEMPLATES = { tryTrial: require('./templates/try-trial.tpl'), limitsExceeded: require('./templates/limits-exceeded.tpl'), closeLimits: require('./templates/close-limits.tpl'), upgradedMessage: require('./templates/upgraded-message.tpl'), trialEndsSoon: require('./templates/trial-ends-soon.tpl') }; const REQUIRED_OPTS = [ 'configModel', 'user' ]; /** * User notifactions view used to show alerts from the application * * In storage we will check these attributes, managed by a collection: * * try_trial -> trial_end_at is null && user is not paid user * limits_exceeded -> check table quota size * close_limits -> check table quota size < 80% * upgraded -> check upgraded_at less than one week * trial_ends_soon -> trial_end_at is not null and it is close to be finished * new_dashboard -> new dashboard * notification -> check notification * */ module.exports = CoreView.extend({ attributes: { href: '#/notifications' }, tagName: 'a', className: 'UserNotifications', events: { 'click': '_openNotifications' }, initialize: function (options) { checkAndBuildOpts(options, REQUIRED_OPTS, this); this.localStorage = new LocalStorage(); this.collection = new UserNotificationsCollection({ configModel: options.configModel }); this.collection.reset(this._generateCollection(), { userId: this._user.get('id'), apiKey: this._user.get('api_key'), silent: true }); this._initBinds(); }, render: function () { var notificationsCount = this.collection.filter(item => !item.get('opened')).length; this.$el.html( template({ notificationsCount: notificationsCount }) ); this.$el.toggleClass('has--alerts', notificationsCount > 0); return this; }, _initBinds: function () { this._user.bind('change', this._onUserChange, this); this.collection.bind('reset', this.render, this); this.collection.bind('remove', this.render, this); this.add_related_model(this._user); this.add_related_model(this.collection); }, _onUserChange: function () { // When api is ready, we will make a valid fetch :) this.collection.reset(this._generateCollection(), { userId: this._user.get('id'), apiKey: this._user.get('api_key') }); this.render(); }, // This method will check notifications and create a collection with them // Also it will check if those have been opened or not with Local Storage. _generateCollection: function () { var arr = []; var data = {}; // data var userUrl = this._user.viewUrl(); var comHosted = this._configModel.get('cartodb_com_hosted'); data.isInsideOrg = this._user.isInsideOrg(); data.isOrgOwner = this._user.isOrgOwner(); data.accountType = this._user.get('account_type').toLowerCase(); data.remainingQuota = this._user.get('remaining_byte_quota'); data.publicProfileUrl = userUrl.publicProfile(); data.bytesQuota = this._user.get('quota_in_bytes'); data.userType = 'regular'; data.upgradeUrl = window.upgrade_url || ''; data.upgradeContactEmail = this._user.upgradeContactEmail(); data.trialEnd = this._user.get('trial_ends_at') && moment(this._user.get('trial_ends_at')).format('YYYY-MM-DD'); data.userName = this._user.get('name') || this._user.get('username'); // Get user type if (data.isInsideOrg && !data.isOrgOwner) { data.userType = 'org'; } else if (data.isOrgOwner) { data.userType = 'admin'; } else if (data.accountType === 'internal' || data.accountType === 'partner' || data.accountType === 'ambassador') { data.userType = 'internal'; } // try_trial -> trial_end_at is null && user is not paid user if (!comHosted && !data.isInsideOrg && data.accountType === 'free' && this._user.get('table_count') > 0) { arr.push({ iconFont: 'CDB-IconFont-gift', severity: 'NotificationsDropdown-itemIcon--positive', type: 'try_trial', msg: TEMPLATES.tryTrial(data), opened: this.localStorage.get('notification.try_trial') }); } else { this.localStorage.remove('notification.try_trial'); } // limits_exceeded -> check table quota size if (!comHosted && data.bytesQuota > 0 && data.remainingQuota <= 0) { arr.push({ iconFont: 'CDB-IconFont-barometer', severity: 'NotificationsDropdown-itemIcon--negative', type: 'limits_exceeded', msg: TEMPLATES.limitsExceeded(data), opened: this.localStorage.get('notification.limits_exceeded') }); } else { this.localStorage.remove('notification.limits_exceeded'); } // close_limits -> check table quota size < 80% if (!comHosted && data.bytesQuota > 0 && ((data.remainingQuota * 100) / data.bytesQuota) < 20) { arr.push({ iconFont: 'CDB-IconFont-barometer', severity: 'NotificationsDropdown-itemIcon--alert', type: 'close_limits', msg: TEMPLATES.closeLimits(data), opened: this.localStorage.get('notification.close_limits') }); } else { this.localStorage.remove('notification.close_limits'); } // upgraded -> check upgraded_at less than ... one week? if (!comHosted && this._user.get('show_upgraded_message')) { arr.push({ iconFont: 'CDB-IconFont-heartFill', severity: 'NotificationsDropdown-itemIcon--positive', type: 'upgraded_message', msg: TEMPLATES.upgradedMessage(data), opened: this.localStorage.get('notification.upgraded_message') }); } else { this.localStorage.remove('notification.upgraded_message'); } // trial_ends_soon -> show_trial_reminder flag if (this._user.get('show_trial_reminder')) { arr.push({ iconFont: 'CDB-IconFont-clock', severity: 'NotificationsDropdown-itemIcon--alert', type: 'trial_ends_soon', msg: TEMPLATES.trialEndsSoon(data), opened: this.localStorage.get('notification.trial_ends_soon') }); } else { this.localStorage.remove('notification.trial_ends_soon'); } const organizationNotifications = window.organization_notifications || this.options.organizationNotifications; if (organizationNotifications) { for (var n = 0; n < organizationNotifications.length; n++) { var notification = organizationNotifications[n]; var icon = notification.icon ? ('CDB-IconFont-' + notification.icon) : 'CDB-IconFont-alert'; arr.push({ iconFont: icon, severity: 'NotificationsDropdown-itemIcon--alert', id: notification.id, msg: notification.html_body, read_at: notification.read_at, type: 'org_notification' }); } } return arr; }, _openNotifications: function (event) { if (event) this.killEvent(event); if (this.notification) { this.notification.hide(); delete this.notification; return this; } var view = this.notification = new NotificationsDropdown({ target: this.$el, collection: this.collection, horizontal_offset: 5, vertical_offset: -5, template: dropdownTemplate }); $(view.options.target).unbind('click', view._handleClick); this._closeAnyOtherOpenDialogs(); view.on('onDropdownHidden', () => this._onDropdownHidden(view)); view.render(); view.open(); this.addView(view); }, _onDropdownHidden: function (view) { // All notifications have been seen, opened -> true this.collection.each(notification => { const notificationType = notification.get('type'); if (notificationType === 'org_notification') { notification.markAsRead(); } else if (notificationType) { notification.set('opened', true); this.localStorage.set({ [`notification.${notificationType}`]: true }); } }); // Clean collection because all notifications should // removed from the collection this.collection.reset(); // Clean dropdown view.clean(); // Remove it from subviews this.removeView(view); // Remove count this.$el.removeClass('has--alerts'); // No local notification set delete this.notification; }, _closeAnyOtherOpenDialogs: function () { // cdb.god.trigger("closeDialogs"); TODO: handle event } });