first run notification settings

This commit is contained in:
zmagod 2016-10-04 15:52:48 +02:00
parent 61cb9651c7
commit a68e7abc57
11 changed files with 271 additions and 89 deletions

View file

@ -21,6 +21,7 @@
//= require bootstrap-datetimepicker
//= require bootstrap-colorselector
//= require bootstrap-tagsinput.min
//= require bootstrap-checkbox.min
//= require typeahead.bundle.min
//= require nested_form_fields
//= require_directory ./sitewide

View file

@ -1,90 +1,131 @@
/**
* Toggle the view/edit form visibility.
* @param form - The jQuery form selector.
* @param edit - True to set form to edit mode;
* false to set form to view mode.
*/
function toggleFormVisibility(form, edit) {
if (edit) {
form.find("[data-part='view']").hide();
form.find("[data-part='edit']").show();
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
} else {
form.find("[data-part='view']").show();
form.find("[data-part='edit'] input").blur();
form.find("[data-part='edit']").hide();
(function(){
'use strict';
/**
* Toggle the view/edit form visibility.
* @param form - The jQuery form selector.
* @param edit - True to set form to edit mode;
* false to set form to view mode.
*/
function toggleFormVisibility(form, edit) {
if (edit) {
form.find("[data-part='view']").hide();
form.find("[data-part='edit']").show();
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
} else {
form.find("[data-part='view']").show();
form.find("[data-part='edit'] input").blur();
form.find("[data-part='edit']").hide();
// Clear all errors on the parent form
form.clearFormErrors();
// Clear all errors on the parent form
form.clearFormErrors();
// Clear any neccesary fields
form.find("input[data-role='clear']").val("");
// Clear any neccesary fields
form.find("input[data-role='clear']").val("");
// Copy field data
var val = form.find("input[data-role='src']").val();
form.find("input[data-role='edit']").val(val);
// Copy field data
var val = form.find("input[data-role='src']").val();
form.find("input[data-role='edit']").val(val);
}
}
}
var forms = $("form[data-for]");
var forms = $("form[data-for]");
// Add "edit form" listeners
forms
.find("[data-action='edit']").click(function() {
var form = $(this).closest("form");
// Add "edit form" listeners
forms
.find("[data-action='edit']").click(function() {
var form = $(this).closest("form");
// First, hide all form edits
_.each(forms, function(form) {
toggleFormVisibility($(form), false);
// First, hide all form edits
_.each(forms, function(form) {
toggleFormVisibility($(form), false);
});
// Then, edit the current form
toggleFormVisibility(form, true);
});
// Then, edit the current form
toggleFormVisibility(form, true);
});
// Add "cancel form" listeners
forms
.find("[data-action='cancel']").click(function() {
var form = $(this).closest("form");
// Add "cancel form" listeners
forms
.find("[data-action='cancel']").click(function() {
var form = $(this).closest("form");
// Hide the edit portion of the form
toggleFormVisibility(form, false);
});
// Hide the edit portion of the form
toggleFormVisibility(form, false);
});
// Add form submit listeners
forms
.on("ajax:success", function(ev, data, status) {
// Simply reload the page
location.reload();
})
.on("ajax:error", function(ev, data, status) {
// Render form errors
$(this).renderFormErrors("user", data.responseJSON);
});
var repeatTutorialModal = $("#repeat-tutorial-modal");
var repeatTutorialModalBody = repeatTutorialModal.find(".modal-body");
initRepeatTutorialModal();
$("#reset-tutorial-btn")
.on("ajax:before", function () {
repeatTutorialModal.modal('show');
// Add form submit listeners
forms
.on("ajax:success", function(ev, data, status) {
// Simply reload the page
location.reload();
})
.on("ajax:success", function (e, data) {
initRepeatTutorialModalBody(data);
.on("ajax:error", function(ev, data, status) {
// Render form errors
$(this).renderFormErrors("user", data.responseJSON);
});
function initRepeatTutorialModal() {
// Remove modal content when modal window is closed.
repeatTutorialModal.on("hidden.bs.modal", function () {
repeatTutorialModalBody.html("");
});
}
var repeatTutorialModal = $("#repeat-tutorial-modal");
var repeatTutorialModalBody = repeatTutorialModal.find(".modal-body");
initRepeatTutorialModal();
notificationsSettings();
initNotificationSettingsForm();
// Initialize ajax listeners and elements style on modal body. This
// function must be called when modal body is changed.
function initRepeatTutorialModalBody(data) {
repeatTutorialModalBody.html(data.html);
repeatTutorialModalBody.find(".selectpicker").selectpicker();
}
$("#reset-tutorial-btn")
.on("ajax:before", function () {
repeatTutorialModal.modal('show');
})
.on("ajax:success", function (e, data) {
initRepeatTutorialModalBody(data);
});
function initRepeatTutorialModal() {
// Remove modal content when modal window is closed.
repeatTutorialModal.on("hidden.bs.modal", function () {
repeatTutorialModalBody.html("");
});
}
// Initialize ajax listeners and elements style on modal body. This
// function must be called when modal body is changed.
function initRepeatTutorialModalBody(data) {
repeatTutorialModalBody.html(data.html);
repeatTutorialModalBody.find(".selectpicker").selectpicker();
}
// Setup notification checkbox buttons
function notificationsSettings() {
var recent_notification = $('[name="recent_notification"]');
recent_notification
.checkboxpicker({
onActiveCls: 'btn-primary'
});
if ( recent_notification.attr('value') === 'true' ) {
recent_notification.prop('checked', true);
} else {
recent_notification.prop('checked', false);
}
var assignments_notification = $('[name="assignments_notification"]');
assignments_notification
.checkboxpicker({
onActiveCls: 'btn-primary'
});
if ( assignments_notification.attr('value') === 'true' ) {
assignments_notification.prop('checked', true);
} else {
assignments_notification.prop('checked', false);
}
}
// triggers submit action when the user clicks
function initNotificationSettingsForm() {
$('#notifications-settings-panel')
.find('.btn-group')
.on('click', function() {
$(this).submit();
});
}
})();

View file

@ -549,6 +549,10 @@ a[data-toggle="tooltip"] {
margin-bottom: 15px;
}
.notification-settings-labels {
line-height: 2em;
margin-top: 40px;
}
// Help link
#help-link {
padding: 13px;

View file

@ -9,7 +9,8 @@ class Users::SettingsController < ApplicationController
:create_organization,
:organization_users_datatable,
:tutorial,
:reset_tutorial
:reset_tutorial,
:notifications_settings
]
before_action :check_organization_permission, only: [
@ -456,6 +457,37 @@ class Users::SettingsController < ApplicationController
end
end
def notifications_settings
if params[:assignments_notification]
@user.assignments_notification = true
else
@user.assignments_notification = false
end
if params[:recent_notification]
@user.recent_notification = true
else
@user.recent_notification = false
end
if @user.save
respond_to do |format|
format.json do
render json: {
status: :ok
}
end
end
else
respond_to do |format|
format.json do
render json: {
status: :unprocessable_entity
}
end
end
end
end
private
def load_user
@ -543,6 +575,9 @@ class Users::SettingsController < ApplicationController
message:
ActionController::Base.helpers.sanitize(message),
)
UserNotification.create(notification: notification, user: target_user)
if target_user.assignments_notification
UserNotification.create(notification: notification, user: target_user)
end
end
end

View file

@ -75,6 +75,10 @@ class Activity < ActiveRecord::Base
project.users.each do |project_user|
next if project_user == user
next if !project_user.assignments_notification &&
notification.type_of == 'assignment'
next if !project_user.recent_notification &&
notification.type_of == 'recent_changes'
UserNotification.create(notification: notification, user: project_user)
end
end

View file

@ -34,6 +34,28 @@
<% if Rails.configuration.x.enable_tutorial %>
<%= link_to t('users.settings.preferences.tutorial.repeat_tutorial'), tutorial_path(format: :json), remote: true, class: "btn btn-primary", id: "reset-tutorial-btn" %>
<% end %>
<div class="container">
<h4><%= t('notifications.title') %></h4>
<%= form_for(@user,
url: notifications_settings_path(format: :json),
html: { method: :post, id: 'notifications-settings-panel' },
remote: true) do |f| %>
<div class="row">
<div class="col-xs-4 text-right">
<div class="notification-settings-labels">
<%= f.label t('notifications.form.recent_notification') %> <br>
<%= f.label t('notifications.form.assignments') %>
</div>
</div>
<div class="col-xs-8">
<h5><%= t('notifications.title') %></h5>
<%= check_box_tag :recent_notification, @user.recent_notification %> <br>
<%= check_box_tag :assignments_notification, @user.assignments_notification %>
</div>
</div>
<% end %>
</div>
</div>
<div class="tab-pane tab-pane-settings" role="tabpanel"></div>
</div>
@ -54,4 +76,6 @@
</div>
</div>
<%= javascript_include_tag "users/settings/preferences" %>
<%= javascript_include_tag "users/settings/preferences" %>

View file

@ -1511,6 +1511,9 @@ en:
notifications:
title: "Notifications"
form:
assignments: "Assignments notifications"
recent_notification: "Change notifications"
# This section contains general words that can be used in any parts of
# application.

View file

@ -15,6 +15,10 @@ Rails.application.routes.draw do
put "users/settings/preferences", to: "users/settings#update_preferences", as: "update_preferences"
get "users/settings/preferences/tutorial", to: "users/settings#tutorial", as: "tutorial"
post "users/settings/preferences/reset_tutorial/", to: "users/settings#reset_tutorial", as: "reset_tutorial"
post 'users/settings/preferences/notifications_settings',
to: 'users/settings#notifications_settings',
as: 'notifications_settings',
defaults: { format: 'json' }
get "users/settings/organizations", to: "users/settings#organizations", as: "organizations"
get "users/settings/organizations/new", to: "users/settings#new_organization", as: "new_organization"
post "users/settings/organizations/new", to: "users/settings#create_organization", as: "create_organization"

View file

@ -0,0 +1,13 @@
class AddNotifivationsSettingsToUser < ActiveRecord::Migration
def up
add_column :users, :assignments_notification, :boolean, default: true
add_column :users, :recent_notification, :boolean, default: true
User.update_all(assignments_notification: true, recent_notification: true)
end
def down
remove_column :users, :assignments_notification
remove_column :users, :recent_notification
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160928114915) do
ActiveRecord::Schema.define(version: 20161004074754) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -63,6 +63,41 @@ ActiveRecord::Schema.define(version: 20160928114915) do
add_index "assets", ["file_file_name"], name: "index_assets_on_file_file_name", using: :gist
add_index "assets", ["last_modified_by_id"], name: "index_assets_on_last_modified_by_id", using: :btree
create_table "billing_accounts", force: :cascade do |t|
t.string "braintree_customer_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "billing_accounts", ["braintree_customer_id"], name: "index_billing_accounts_on_braintree_customer_id", using: :btree
create_table "billing_plans", force: :cascade do |t|
t.string "braintree_plan_id", null: false
t.string "name", null: false
t.string "description"
t.integer "price_cents", default: 0, null: false
t.string "price_currency", default: "USD", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "main", default: false, null: false
t.integer "max_storage", limit: 8, default: 0
t.integer "position", default: 0, null: false
t.boolean "free", default: false, null: false
end
add_index "billing_plans", ["braintree_plan_id"], name: "index_billing_plans_on_braintree_plan_id", using: :btree
create_table "billing_subscriptions", force: :cascade do |t|
t.string "braintree_subscription_id"
t.integer "billing_account_id", null: false
t.integer "billing_plan_id", null: false
t.boolean "active", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "billing_subscriptions", ["braintree_subscription_id"], name: "index_billing_subscriptions_on_braintree_subscription_id", using: :btree
create_table "checklist_items", force: :cascade do |t|
t.string "text", null: false
t.boolean "checked", default: false, null: false
@ -245,8 +280,11 @@ ActiveRecord::Schema.define(version: 20160928114915) do
t.integer "last_modified_by_id"
t.string "description"
t.integer "space_taken", limit: 8, default: 1048576, null: false
t.integer "billing_account_id"
t.integer "agile_crm_deal_id", limit: 8
end
add_index "organizations", ["billing_account_id"], name: "index_organizations_on_billing_account_id", using: :btree
add_index "organizations", ["created_by_id"], name: "index_organizations_on_created_by_id", using: :btree
add_index "organizations", ["last_modified_by_id"], name: "index_organizations_on_last_modified_by_id", using: :btree
add_index "organizations", ["name"], name: "index_organizations_on_name", using: :btree
@ -627,20 +665,20 @@ ActiveRecord::Schema.define(version: 20160928114915) do
add_index "user_projects", ["user_id"], name: "index_user_projects_on_user_id", using: :btree
create_table "users", force: :cascade do |t|
t.string "full_name", null: false
t.string "initials", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "full_name", null: false
t.string "initials", null: false
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
@ -649,7 +687,7 @@ ActiveRecord::Schema.define(version: 20160928114915) do
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.string "time_zone", default: "UTC"
t.string "time_zone", default: "UTC"
t.string "invitation_token"
t.datetime "invitation_created_at"
t.datetime "invitation_sent_at"
@ -657,8 +695,13 @@ ActiveRecord::Schema.define(version: 20160928114915) do
t.integer "invitation_limit"
t.integer "invited_by_id"
t.string "invited_by_type"
t.integer "invitations_count", default: 0
t.integer "tutorial_status", default: 0, null: false
t.integer "invitations_count", default: 0
t.integer "tutorial_status", default: 0, null: false
t.integer "initial_billing_plan_id"
t.datetime "last_seen_at"
t.integer "agile_crm_contact_id", limit: 8
t.boolean "assignments_notification", default: true
t.boolean "recent_notification", default: true
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
@ -674,6 +717,8 @@ ActiveRecord::Schema.define(version: 20160928114915) do
add_foreign_key "asset_text_data", "assets"
add_foreign_key "assets", "users", column: "created_by_id"
add_foreign_key "assets", "users", column: "last_modified_by_id"
add_foreign_key "billing_subscriptions", "billing_accounts"
add_foreign_key "billing_subscriptions", "billing_plans"
add_foreign_key "checklist_items", "checklists"
add_foreign_key "checklist_items", "users", column: "created_by_id"
add_foreign_key "checklist_items", "users", column: "last_modified_by_id"
@ -704,6 +749,7 @@ ActiveRecord::Schema.define(version: 20160928114915) do
add_foreign_key "my_modules", "users", column: "last_modified_by_id"
add_foreign_key "my_modules", "users", column: "restored_by_id"
add_foreign_key "notifications", "users", column: "generator_user_id"
add_foreign_key "organizations", "billing_accounts"
add_foreign_key "organizations", "users", column: "created_by_id"
add_foreign_key "organizations", "users", column: "last_modified_by_id"
add_foreign_key "project_comments", "comments"

View file

@ -0,0 +1,7 @@
/*!
* Bootstrap-checkbox v1.4.0 (https://vsn4ik.github.io/bootstrap-checkbox/)
* Copyright 2013-2016 Vasily A. (https://github.com/vsn4ik)
* Licensed under the MIT license
*/
"use strict";!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){function b(b,c){this.element=b,this.$element=a(b);var d=this.$element.data();return""===d.reverse&&(d.reverse=!0),""===d.switchAlways&&(d.switchAlways=!0),""===d.html&&(d.html=!0),this.options=a.extend({},a.fn.checkboxpicker.defaults,c,d),this.$element.closest("label").length?void console.warn(this.options.warningMessage):(this.$group=a.create("div"),this.$buttons=a.create("a","a"),this.$off=this.$buttons.eq(this.options.reverse?1:0),this.$on=this.$buttons.eq(this.options.reverse?0:1),void this.init())}a.create=function(){return a(a.map(arguments,a.proxy(document,"createElement")))},b.prototype={init:function(){var b=this.options.html?"html":"text";this.$element.addClass("hidden"),this.$group.addClass(this.options.baseGroupCls).addClass(this.options.groupCls),this.$buttons.addClass(this.options.baseCls).addClass(this.options.cls),this.options.offLabel&&this.$off[b](this.options.offLabel),this.options.onLabel&&this.$on[b](this.options.onLabel),this.options.offIconCls&&(this.options.offLabel&&this.$off.prepend("&nbsp;"),a.create("span").addClass(this.options.iconCls).addClass(this.options.offIconCls).prependTo(this.$off)),this.options.onIconCls&&(this.options.onLabel&&this.$on.prepend("&nbsp;"),a.create("span").addClass(this.options.iconCls).addClass(this.options.onIconCls).prependTo(this.$on)),this.element.checked?(this.$on.addClass("active"),this.$on.addClass(this.options.onActiveCls),this.$off.addClass(this.options.offCls)):(this.$off.addClass("active"),this.$off.addClass(this.options.offActiveCls),this.$on.addClass(this.options.onCls)),this.element.title?this.$group.attr("title",this.element.title):(this.options.offTitle&&this.$off.attr("title",this.options.offTitle),this.options.onTitle&&this.$on.attr("title",this.options.onTitle)),this.$group.on("keydown",a.proxy(this,"keydown")),this.$buttons.on("click",a.proxy(this,"click")),this.$element.on("change",a.proxy(this,"toggleChecked")),a(this.element.labels).on("click",a.proxy(this,"focus")),a(this.element.form).on("reset",a.proxy(this,"reset")),this.$group.append(this.$buttons).insertAfter(this.element),this.element.disabled?(this.$buttons.addClass("disabled"),this.options.disabledCursor&&this.$group.css("cursor",this.options.disabledCursor)):(this.$group.attr("tabindex",this.element.tabIndex),this.element.autofocus&&this.focus())},toggleChecked:function(){this.$buttons.toggleClass("active"),this.$off.toggleClass(this.options.offCls),this.$off.toggleClass(this.options.offActiveCls),this.$on.toggleClass(this.options.onCls),this.$on.toggleClass(this.options.onActiveCls)},toggleDisabled:function(){this.$buttons.toggleClass("disabled"),this.element.disabled?(this.$group.attr("tabindex",this.element.tabIndex),this.$group.css("cursor","")):(this.$group.removeAttr("tabindex"),this.options.disabledCursor&&this.$group.css("cursor",this.options.disabledCursor))},focus:function(){this.$group.trigger("focus")},click:function(b){var c=a(b.currentTarget);c.hasClass("active")&&!this.options.switchAlways||this.change()},change:function(){this.set(!this.element.checked)},set:function(a){this.element.checked=a,this.$element.trigger("change")},keydown:function(b){-1!=a.inArray(b.keyCode,this.options.toggleKeyCodes)?(b.preventDefault(),this.change()):13==b.keyCode&&a(this.element.form).trigger("submit")},reset:function(){(this.element.defaultChecked&&this.$off.hasClass("active")||!this.element.defaultChecked&&this.$on.hasClass("active"))&&this.set(this.element.defaultChecked)}};var c=a.extend({},a.propHooks);a.extend(a.propHooks,{checked:{set:function(b,d){var e=a.data(b,"bs.checkbox");e&&b.checked!=d&&e.change(d),c.checked&&c.checked.set&&c.checked.set(b,d)}},disabled:{set:function(b,d){var e=a.data(b,"bs.checkbox");e&&b.disabled!=d&&e.toggleDisabled(),c.disabled&&c.disabled.set&&c.disabled.set(b,d)}}});var d=a.fn.checkboxpicker;return a.fn.checkboxpicker=function(c,d){var e;return e=this instanceof a?this:a("string"==typeof c?c:d),e.each(function(){var d=a.data(this,"bs.checkbox");d||(d=new b(this,c),a.data(this,"bs.checkbox",d))})},a.fn.checkboxpicker.defaults={baseGroupCls:"btn-group",baseCls:"btn",groupCls:null,cls:null,offCls:"btn-default",onCls:"btn-default",offActiveCls:"btn-danger",onActiveCls:"btn-success",offLabel:"No",onLabel:"Yes",offTitle:!1,onTitle:!1,iconCls:"glyphicon",disabledCursor:"not-allowed",toggleKeyCodes:[13,32],warningMessage:"Please do not use Bootstrap-checkbox element in label element."},a.fn.checkboxpicker.Constructor=b,a.fn.checkboxpicker.noConflict=function(){return a.fn.checkboxpicker=d,this},a.fn.checkboxpicker});