tb_core 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -51
  3. data/app/assets/javascripts/admin/core/application.js +1 -5
  4. data/app/assets/javascripts/tb_core/remote.js +179 -0
  5. data/app/assets/libs/jquery-ui/jquery-ui.scss +16 -16
  6. data/app/assets/stylesheets/admin/core/{application.css.scss → application.scss} +22 -0
  7. data/app/assets/stylesheets/admin/core/{login.css.scss → login.scss} +0 -0
  8. data/app/controllers/admin/application_controller.rb +6 -0
  9. data/app/controllers/admin/users_controller.rb +4 -10
  10. data/app/controllers/spud/application_controller.rb +8 -2
  11. data/app/controllers/user_sessions_controller.rb +1 -1
  12. data/app/helpers/tb_core/application_helper.rb +88 -0
  13. data/app/views/admin/users/_form.html.erb +21 -17
  14. data/app/views/admin/users/edit.html.erb +1 -3
  15. data/app/views/admin/users/new.html.erb +1 -3
  16. data/app/views/layouts/admin/_search.html.erb +3 -0
  17. data/app/views/layouts/admin/detail.html.erb +1 -1
  18. data/app/views/layouts/admin/error_page.html.erb +10 -12
  19. data/config/locales/en.yml +8 -0
  20. data/lib/generators/spud/controller_spec_generator.rb +34 -0
  21. data/lib/generators/spud/module_generator.rb +37 -0
  22. data/lib/generators/spud/setup_generator.rb +5 -0
  23. data/lib/generators/spud/templates/admin_controller.rb.erb +2 -7
  24. data/lib/generators/spud/templates/assets/application.scss +1 -0
  25. data/lib/generators/spud/templates/controller.rb.erb +1 -5
  26. data/lib/generators/spud/templates/controller_spec.rb.erb +79 -0
  27. data/lib/generators/spud/templates/views/admin/_form.html.erb +2 -2
  28. data/lib/generators/spud/templates/views/layouts/application.html.erb +2 -3
  29. data/lib/spud_core/belongs_to_app.rb +1 -1
  30. data/lib/spud_core/engine.rb +2 -0
  31. data/lib/spud_core/errors.rb +23 -14
  32. data/lib/spud_core/version.rb +1 -1
  33. data/lib/tb_core/form_builder.rb +11 -1
  34. data/lib/tb_core/responder.rb +32 -0
  35. data/lib/tb_core/test_helper.rb +42 -0
  36. data/spec/controllers/admin/dashboard_controller_spec.rb +3 -7
  37. data/spec/controllers/admin/settings_controller_spec.rb +1 -4
  38. data/spec/controllers/admin/user_sessions_controller_spec.rb +1 -2
  39. data/spec/controllers/admin/users_controller_spec.rb +1 -4
  40. data/spec/dummy/app/assets/stylesheets/admin/{application.css.scss → application.scss} +0 -0
  41. data/spec/dummy/app/assets/stylesheets/{application.css.scss → application.scss} +0 -0
  42. data/spec/rails_helper.rb +2 -1
  43. metadata +14 -14
  44. data/app/helpers/spud/application_helper.rb +0 -36
  45. data/app/helpers/twice_baked/application_helper.rb +0 -42
  46. data/spec/authlogic_helper.rb +0 -2
  47. data/spec/helpers/spud/application_helper_spec.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e9077f254c8e4707711b4b8a4bb521f0824ba95a
4
- data.tar.gz: f858ff8a950238aa2f6f1cf050d5a78fcaab46ea
3
+ metadata.gz: 71aea9ddb3c9316dc739b01f90a9d4fc691de43f
4
+ data.tar.gz: 2c6513d8c1deca712ffee16191abc4a60d1d0ace
5
5
  SHA512:
6
- metadata.gz: d671387317ffd0777cc42c41e17b86dda448e4651b90e6a10aab842073aab993a7864125856f87fe7672ddf5df7fe32a81dc7e1a118d2c4827661d60d60d0895
7
- data.tar.gz: b187eb595c18f78588d03c45b0c2dbc308493e18c0773992713e216643d4009d639111e73ff2637f8b0bf5a3f5b4b31bdc304484ecbc592c94fedd68d7ec89dc
6
+ metadata.gz: eab1674d55e9a78198f6b740e1e3ba26bb62805e0dde9382c68f110794b81db84bc70865d26c4a3e18a1ca98d4761aa9d71759552ceb6d04e0e43a1747428147
7
+ data.tar.gz: cd40971446fe52fdeafedfa2e653701ced4d23843b0592902c2e2d1f56a771585db6b413f5b08e7fc66bc363710b4caefbcfe8720f18f83eb2e774d5d3273687
data/README.md CHANGED
@@ -52,11 +52,19 @@ The core engine provides various configuration options.
52
52
  Adding Apps to the Dashboard
53
53
  ----------------------------
54
54
 
55
- The fastest way to add an app to the dashboard is via the geneator. The generator will take care of creating a model as well as a set of controllers/views.
55
+ ### Using the Module Generator
56
+
57
+ The fastest way to add an app to the dashboard is via the geneator. The generator is best suited for situations where you are also creating a new ActiveRecord model, and you want to create a standard set of CRUD actions to manage it.
56
58
 
57
59
  rails g spud:module Cars make:string model:string year:integer classic:boolean notes:text
58
60
 
59
- Admin apps are added via the `Spud::Core.config.admin_applications` array. We recommend you perform this action in either an initializer or within your `application.rb` file.
61
+ Running the example above would create an ActiveRecord model, migration, admin controller with CRUD actions, and a set of front end views.
62
+
63
+ ### Adding Manually
64
+
65
+ Sometimes you want to create a dashboard app without all the magic described above. Start by building your controllers and views under the `admin` namespace. Extend your controller from `Admin::ApplicationController` in order to inherit the default admin behaviors, and look at the core admin controllers for example implementations.
66
+
67
+ Then, add your module to the `Spud::Core.config.admin_applications` array. We recommend you perform this action in either an initializer or within your `application.rb` file.
60
68
 
61
69
  Spud::Core.config.admin_applications += [{
62
70
  # Required:
@@ -67,43 +75,7 @@ Admin apps are added via the `Spud::Core.config.admin_applications` array. We re
67
75
  :badge => ->(user){ Client.where(:is_pending => true).count() }
68
76
  }]
69
77
 
70
- Build out your RESTful controller and views like you normally would in any Rails app. Extend your controller from `Admin::ApplicationController` in order to inherit the default admin behaviors, and look at the core admin controllers for example implementations.
71
-
72
- Roles & Permissions
73
- -------------------
74
-
75
- A user can be assigned to a role, which itself has one or more permissions. These permissions serve two distinct purposes: Determining what dashboard apps a user has access to (if any), and being used within your application logic to establish fine-grained user abilities.
76
-
77
- Checking permissions is easy. Each permission has a `tag` which is a unique, namespaced identifier. Use the `has_permission?` and `has_any_permission?` methods on the user object to check for their existence. Keep in mind that these calls will always return true for users who have the `super_admin` flag set.
78
-
79
- if !@spud_user.has_permission?('my_website.clients.my_clients')
80
- redirect_to root_path, :notice => 'Permission Denied!'
81
- end
82
-
83
- Permissions are created one of two ways:
84
-
85
- 1. Every dashboard app automatically generates a "Full Access" permission, with a tag of `admin.(app name).full_access`.
86
- 2. You as the developer may append custom permissions to the `Spud::Core.permissions` array.
87
-
88
- Create custom permissions whenever you need to permit an action that falls outside of the standard "full access to an app" use case; For example, turning on/off the ability for a user to upload an avatar.
89
-
90
- // application.rb
91
- Spud::Core.permissions += [
92
- {:tag => 'my_website.profile.avatar', :name => 'Upload an avatar to my profile'}
93
- ]
94
- // some_view.html.erb
95
- <% if current_user.has_permission?('my_website.profile.avatar') %>
96
- <%= link_to 'Upload Avatar', upload_avatar_path %>
97
- <% end %>
98
-
99
- Finally, custom permissions may optionally be tied to one or more dashboard apps. A user who has the permission shown below would have access to the the Clients and Projects dashboard apps. After that is is up to you to code your view and controller logic in accorance to what permissions the user has.
100
-
101
- // application.rb
102
- Spud::Core.permissions += [{
103
- :tag => 'my_website.projects.project_management',
104
- :name => 'Manage clients and projects, but cannot delete them or view private info',
105
- :apps => [:clients, :projects]
106
- }]
78
+ Restart your application, and the new module should appear on the dashboard.
107
79
 
108
80
  Extending the User Model
109
81
  ------------------------
@@ -144,28 +116,111 @@ Create a file in your app at `app/views/admin/users/_show_additions.html.erb`.
144
116
  </dd>
145
117
  <% end %>
146
118
 
147
- 404 Handling
119
+ Error Handling
148
120
  ------------
149
121
 
150
- The base `Spud::ApplicationController` will automatically rescue from any `Spud::NotFoundError` and `Spud::AccessDeniedError` errors by rendering out the `layouts/error_page.html` template. To customize this view, create a template by that name in your own project.
122
+ The base `Spud::ApplicationController` will automatically rescue from any `Spud::NotFoundError` and `Spud::AccessDeniedError` errors by rendering out the `layouts/error_page.html` template. If you ran the `spud:setup` generator, a template of that name will have been created for you automatically.
151
123
 
152
- When building your own apps you may raise a `Spud::NotFoundError` at any time to halt further execution and cause the 404 page to render. For example:
124
+ When building your own apps you may raise a `Spud::NotFoundError` or `Spud::AccessDeniedError` error at any time to halt further execution and cause the error page to render. For example:
153
125
 
154
126
  class CarsController
155
127
  before_action :get_record
156
128
  # ... actions here
157
129
  def get_record
158
- @car = Car.where(:id => params[:id]).first
130
+ @car = Car.find_by(:id => params[:id])
159
131
  if @car.blank?
160
- raise Spud::NotFoundError.new(:item => 'car')
132
+ raise Spud::NotFoundError.new('car')
161
133
  end
162
134
  end
163
135
  end
164
136
 
137
+ Error messages also support localization. Add the following to `en.yml`, as well as any other locales you are using:
138
+
139
+ en:
140
+ tb_core:
141
+ errors:
142
+ not_found:
143
+ title: "Not Found"
144
+ message: "The %{item} you were looking for could not be found."
145
+ access_denied:
146
+ title: "Access Denied"
147
+ message: "You are not authorized to view the requested %{item}."
148
+
149
+ Form Builder
150
+ ------------
151
+
152
+ The `TbCore::FormBuilder` class is designed to output a Bootstrap form in the `form-horizontal` style. Use it to quickly build simple forms with lots of fields. Validation errors will appear automatically next to their input fields.
153
+
154
+ You can specificy the `:builder` attribute in a `form_for`, or you can just use the provided `tb_form_for` helper to use it automatically.
155
+
156
+ ### Example
157
+
158
+ <%= tb_form_for @widget do |f| %>
159
+ <%= f.tb_text_field :name %>
160
+ <%= f.tb_number_field :age %>
161
+ <%= f.tb_save_buttons('Widget', widgets_path) %>
162
+ <% end %>
163
+
164
+ See `form_builder.rb` for the full details.
165
+
166
+ Remote Forms
167
+ --------------
168
+
169
+ If you choose to include the `tb_core` JavaScript file in your project, we provide a couple additional features on top of the standard [jquery-ujs](https://github.com/rails/jquery-ujs) adapter. Use the following data attributes on your remote forms.
170
+
171
+ - `data-errors`: Set to `inline` for inline form errors, or `alert` for a browser alert window.
172
+ - `data-success`: Set to a path you would like to redirect to. For example, redirecting back to an index after a successful post.
173
+
174
+ This feature works with `tb_form_for` or standard `form_for` forms.
175
+
176
+ ### Example
177
+
178
+ <%= tb_form_for @widget, :remote => true, :data => {:errors => :inline, :success => widgets_path} do |f| %>
179
+ <!--input fields here -->
180
+ <% end %>
181
+
182
+ See `remote.js` for the full details.
183
+
184
+ Roles & Permissions
185
+ -------------------
186
+
187
+ A user can be assigned to a role, which itself has one or more permissions. These permissions serve two distinct purposes: Determining what dashboard apps a user has access to (if any), and being used within your application logic to establish fine-grained user abilities.
188
+
189
+ Checking permissions is easy. Each permission has a `tag` which is a unique, namespaced identifier. Use the `has_permission?` and `has_any_permission?` methods on the user object to check for their existence. Keep in mind that these calls will always return true for users who have the `super_admin` flag set.
190
+
191
+ if !@spud_user.has_permission?('my_website.clients.my_clients')
192
+ redirect_to root_path, :notice => 'Permission Denied!'
193
+ end
194
+
195
+ Permissions are created one of two ways:
196
+
197
+ 1. Every dashboard app automatically generates a "Full Access" permission, with a tag of `admin.(app name).full_access`.
198
+ 2. You as the developer may append custom permissions to the `Spud::Core.permissions` array.
199
+
200
+ Create custom permissions whenever you need to permit an action that falls outside of the standard "full access to an app" use case; For example, turning on/off the ability for a user to upload an avatar.
201
+
202
+ // application.rb
203
+ Spud::Core.permissions += [
204
+ {:tag => 'my_website.profile.avatar', :name => 'Upload an avatar to my profile'}
205
+ ]
206
+ // some_view.html.erb
207
+ <% if current_user.has_permission?('my_website.profile.avatar') %>
208
+ <%= link_to 'Upload Avatar', upload_avatar_path %>
209
+ <% end %>
210
+
211
+ Finally, custom permissions may optionally be tied to one or more dashboard apps. A user who has the permission shown below would have access to the the Clients and Projects dashboard apps. After that is is up to you to code your view and controller logic in accorance to what permissions the user has.
212
+
213
+ // application.rb
214
+ Spud::Core.permissions += [{
215
+ :tag => 'my_website.projects.project_management',
216
+ :name => 'Manage clients and projects, but cannot delete them or view private info',
217
+ :apps => [:clients, :projects]
218
+ }]
219
+
165
220
  Testing
166
221
  -----------------
167
222
 
168
- Twice Baked uses RSpec, Capybara and Jasmine for testing. Get the tests running with a few short commands:
223
+ Twice Baked uses RSpec for testing. Get the tests running with a few short commands:
169
224
 
170
225
  1. Create and migrate the databases:
171
226
 
@@ -182,9 +237,4 @@ Twice Baked uses RSpec, Capybara and Jasmine for testing. Get the tests running
182
237
 
183
238
  After the tests have completed the current code coverage stats is available by opening `/coverage/index.html` in a browser.
184
239
 
185
- ### Jasmine
186
-
187
- For running the javascript unit tets:
188
-
189
- rake jasmine:ci
190
-
240
+ You can include a few basic test helpers in your own twice baked applications by requiring `tb_core/test_helper` in your specs.
@@ -37,8 +37,4 @@ $(document).ready(function() {
37
37
  return false;
38
38
  });
39
39
 
40
- $('a.button').button();
41
-
42
- $('input[type=submit].btn').click(function() {$(this).button('loading');});
43
-
44
- } );
40
+ });
@@ -0,0 +1,179 @@
1
+ (function(){
2
+
3
+ /*
4
+ * jQuery UJS Handlers
5
+ *
6
+ * These handlers are designed to work with the Rails/jQuery UJS adapter. This applies
7
+ * to things like data-remote forms, data-method links, etc.
8
+ *
9
+ * For more info see the following lnks:
10
+ * - https://github.com/rails/jquery-ujs/wiki/ajax
11
+ * - http://guides.rubyonrails.org/working_with_javascript_in_rails.html
12
+ *
13
+ */
14
+ tb.remote = {
15
+ init: function(){
16
+ $('body').on('ajax:before', 'form', onRemoteFormBefore);
17
+ $('body').on('ajax:complete', 'form', onRemoteFormComplete);
18
+ $('body').on('ajax:success', 'form[data-success]', onRemoteFormSuccess);
19
+ $('body').on('ajax:error', 'form[data-errors]', onRemoteFormErrors);
20
+ $('body').on('ajax:success', 'tr a[data-method=delete][data-remote=true]', onRemoteDeleteTableRow);
21
+ }
22
+ };
23
+
24
+ /*
25
+ * UJS likes to request application/javascript by default, but Twice Baked prefers JSON.
26
+ */
27
+ var onRemoteFormBefore = function(xhr, settings){
28
+ if($(this).data('type') === undefined){
29
+ $(this).data('type', 'json');
30
+ }
31
+ };
32
+
33
+ /*
34
+ * Runs any time a remote form completes and assigns a true/false value
35
+ * to lastRemoteSuccess depending on if the post was succesful. This is
36
+ * referenced later on in enableFormElement
37
+ */
38
+ var onRemoteFormComplete = function(event, jqXHR, textStatus){
39
+ var success = jqXHR.status >= 200 && jqXHR.status < 300;
40
+ $(this).data('lastRemoteSuccess', success);
41
+ };
42
+
43
+ /*
44
+ * Called when a remote form is submitted with a data-success configured
45
+ * Simply redirects to the configured path, or reloads the page if "reload" is passed
46
+ *
47
+ * ie:
48
+ * <form action="..." data-remote="true" data-success="/path/for/success/:id">
49
+ */
50
+ var onRemoteFormSuccess = function(event, json, textStatus, jqXHR){
51
+ var successPath = $(this).data('success');
52
+ if(successPath == 'reload'){
53
+ window.location.reload();
54
+ }
55
+ else{
56
+ if(json && json.id){
57
+ successPath = successPath.replace(':id', json.id);
58
+ }
59
+ window.location = successPath;
60
+ }
61
+ };
62
+
63
+ /*
64
+ * Called when a remote form is submitted and the server has returned an error, only
65
+ * if the form has a data-errors attribute
66
+ *
67
+ * ie:
68
+ * <form action="..." data-remote="true" data-errors="inline">
69
+ * - OR -
70
+ * <form action="..." data-remote="true" data-errors="alert">
71
+ */
72
+ var onRemoteFormErrors = function(event, jqXHR, textStatus, errorThrown){
73
+ var $form = $(this);
74
+ var errorType = $form.data('errors');
75
+ if(jqXHR.status == 422){
76
+ if(errorType == 'inline'){
77
+ displayErrorsInline($form, jqXHR.responseJSON.errors);
78
+ }
79
+ else{
80
+ displayErrorsAlert(jqXHR.responseJSON.full_messages);
81
+ }
82
+ }
83
+ else if(jqXHR.status == 401 || jqXHR.status == 403){
84
+ window.alert('Error: Access denied.');
85
+ }
86
+ else{
87
+ window.alert('An unexpected error occurred: ' + errorThrown);
88
+ }
89
+ };
90
+
91
+ /*
92
+ * Append error text to form input fields and scroll to the first error
93
+ */
94
+ var displayErrorsInline = function($form, errors){
95
+ $form.find('.form-error-inline, .form-error-base').remove();
96
+ for(var key in errors){
97
+ if(key == 'base'){
98
+ for(var i=0; i<errors[key].length; i++){
99
+ $form.prepend('<p class="form-error form-error-base">'+errors[key][i]+'</p>');
100
+ }
101
+ }
102
+ else{
103
+ var inputName, $input;
104
+
105
+ keyArr = key.split('.');
106
+
107
+ if (keyArr.length === 2) {
108
+ keyArr[0] += '_attributes';
109
+ $input = $("[name$='[" + keyArr.join('][') + "]']");
110
+ } else {
111
+ $input = $("[name$='[" + key + "]']");
112
+ }
113
+
114
+ if($input.length > 0){
115
+ var message = errors[key][0];
116
+ $input.after('<p class="form-error form-error-inline">'+message+'</p>');
117
+ }
118
+ else{
119
+ console.warn('Missing input field for key:', key);
120
+ }
121
+ }
122
+ }
123
+ var $firstError = $form.find('.form-error-inline, .form-error-base').first().parent('div');
124
+ if($firstError.length > 0){
125
+ $('body, html').animate({
126
+ scrollTop: $firstError.offset().top
127
+ });
128
+ }
129
+ };
130
+
131
+ /*
132
+ * Display errors in a standard window.alert dialog
133
+ */
134
+ var displayErrorsAlert = function(errors){
135
+ var text = "Please correct the following errors:\n";
136
+ var len = errors.length;
137
+ for(var i=0; i<len; i++){
138
+ text += " - " + errors[i] + "\n";
139
+ }
140
+ window.alert(text);
141
+ };
142
+
143
+ /*
144
+ * Used for deleting table rows
145
+ * Add a link with data-remote=true and data-method=delete to your table to trigger this callback
146
+ */
147
+ var onRemoteDeleteTableRow = function(){
148
+ var row = $(this).parents('tr');
149
+ row.fadeOut(200, function(){
150
+ row.remove();
151
+ });
152
+ };
153
+
154
+ /*
155
+ * Monkeypatch the base $.rails.enableFormElement function to add a
156
+ * delay and a success text value to the button before returning to
157
+ * default state.
158
+ */
159
+ var originalEnableFormElement = $.rails.enableFormElement;
160
+
161
+ $.rails.enableFormElement = function(element){
162
+ var success = $(element).parents('form').data('lastRemoteSuccess');
163
+ var message = element.data('enableWith');
164
+
165
+ if(success && message){
166
+ var method = element.is('button') ? 'html' : 'val';
167
+ element[method](message);
168
+ setTimeout(function(){
169
+ originalEnableFormElement(element);
170
+ }, 1000);
171
+ }
172
+ else{
173
+ originalEnableFormElement(element);
174
+ }
175
+ };
176
+
177
+ $(document).ready(tb.remote.init);
178
+
179
+ })();
@@ -404,7 +404,7 @@
404
404
  }
405
405
  .ui-widget-content {
406
406
  border: 1px solid #aaaaaa;
407
- background: #ffffff image-url("jquery-ui/images//ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;
407
+ background: #ffffff image-url("jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x;
408
408
  color: #222222;
409
409
  }
410
410
  .ui-widget-content a {
@@ -412,7 +412,7 @@
412
412
  }
413
413
  .ui-widget-header {
414
414
  border: 1px solid #aaaaaa;
415
- background: #cccccc image-url("jquery-ui/images//ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
415
+ background: #cccccc image-url("jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
416
416
  color: #222222;
417
417
  font-weight: bold;
418
418
  }
@@ -426,7 +426,7 @@
426
426
  .ui-widget-content .ui-state-default,
427
427
  .ui-widget-header .ui-state-default {
428
428
  border: 1px solid #d3d3d3;
429
- background: #e6e6e6 image-url("jquery-ui/images//ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
429
+ background: #e6e6e6 image-url("jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
430
430
  font-weight: normal;
431
431
  color: #555555;
432
432
  }
@@ -443,7 +443,7 @@
443
443
  .ui-widget-content .ui-state-focus,
444
444
  .ui-widget-header .ui-state-focus {
445
445
  border: 1px solid #999999;
446
- background: #dadada image-url("jquery-ui/images//ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
446
+ background: #dadada image-url("jquery-ui/images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
447
447
  font-weight: normal;
448
448
  color: #212121;
449
449
  }
@@ -462,7 +462,7 @@
462
462
  .ui-widget-content .ui-state-active,
463
463
  .ui-widget-header .ui-state-active {
464
464
  border: 1px solid #aaaaaa;
465
- background: #ffffff image-url("jquery-ui/images//ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
465
+ background: #ffffff image-url("jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
466
466
  font-weight: normal;
467
467
  color: #212121;
468
468
  }
@@ -479,7 +479,7 @@
479
479
  .ui-widget-content .ui-state-highlight,
480
480
  .ui-widget-header .ui-state-highlight {
481
481
  border: 1px solid #fcefa1;
482
- background: #fbf9ee image-url("jquery-ui/images//ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
482
+ background: #fbf9ee image-url("jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
483
483
  color: #363636;
484
484
  }
485
485
  .ui-state-highlight a,
@@ -491,7 +491,7 @@
491
491
  .ui-widget-content .ui-state-error,
492
492
  .ui-widget-header .ui-state-error {
493
493
  border: 1px solid #cd0a0a;
494
- background: #fef1ec image-url("jquery-ui/images//ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
494
+ background: #fef1ec image-url("jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
495
495
  color: #cd0a0a;
496
496
  }
497
497
  .ui-state-error a,
@@ -537,27 +537,27 @@
537
537
  }
538
538
  .ui-icon,
539
539
  .ui-widget-content .ui-icon {
540
- background-image: image-url("jquery-ui/images//ui-icons_222222_256x240.png");
540
+ background-image: image-url("jquery-ui/images/ui-icons_222222_256x240.png");
541
541
  }
542
542
  .ui-widget-header .ui-icon {
543
- background-image: image-url("jquery-ui/images//ui-icons_222222_256x240.png");
543
+ background-image: image-url("jquery-ui/images/ui-icons_222222_256x240.png");
544
544
  }
545
545
  .ui-state-default .ui-icon {
546
- background-image: image-url("jquery-ui/images//ui-icons_888888_256x240.png");
546
+ background-image: image-url("jquery-ui/images/ui-icons_888888_256x240.png");
547
547
  }
548
548
  .ui-state-hover .ui-icon,
549
549
  .ui-state-focus .ui-icon {
550
- background-image: image-url("jquery-ui/images//ui-icons_454545_256x240.png");
550
+ background-image: image-url("jquery-ui/images/ui-icons_454545_256x240.png");
551
551
  }
552
552
  .ui-state-active .ui-icon {
553
- background-image: image-url("jquery-ui/images//ui-icons_454545_256x240.png");
553
+ background-image: image-url("jquery-ui/images/ui-icons_454545_256x240.png");
554
554
  }
555
555
  .ui-state-highlight .ui-icon {
556
- background-image: image-url("jquery-ui/images//ui-icons_2e83ff_256x240.png");
556
+ background-image: image-url("jquery-ui/images/ui-icons_2e83ff_256x240.png");
557
557
  }
558
558
  .ui-state-error .ui-icon,
559
559
  .ui-state-error-text .ui-icon {
560
- background-image: image-url("jquery-ui/images//ui-icons_cd0a0a_256x240.png");
560
+ background-image: image-url("jquery-ui/images/ui-icons_cd0a0a_256x240.png");
561
561
  }
562
562
 
563
563
  // positioning */
@@ -770,14 +770,14 @@
770
770
 
771
771
  // Overlays */
772
772
  .ui-widget-overlay {
773
- background: #aaaaaa image-url("jquery-ui/images//ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
773
+ background: #aaaaaa image-url("jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
774
774
  opacity: .3;
775
775
  filter: Alpha(Opacity=30); // support: IE8 */
776
776
  }
777
777
  .ui-widget-shadow {
778
778
  margin: -8px 0 0 -8px;
779
779
  padding: 8px;
780
- background: #aaaaaa image-url("jquery-ui/images//ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
780
+ background: #aaaaaa image-url("jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x;
781
781
  opacity: .3;
782
782
  filter: Alpha(Opacity=30); // support: IE8 */
783
783
  border-radius: 8px;
@@ -72,6 +72,9 @@ body {
72
72
  }
73
73
 
74
74
  // Detail
75
+ .nav-tabs{
76
+ margin-bottom: 20px;
77
+ }
75
78
  .admin-search-form{
76
79
  display: inline;
77
80
  }
@@ -102,12 +105,31 @@ body {
102
105
  }
103
106
 
104
107
  // Forms
108
+
109
+ // Old Errors
105
110
  div.field_with_errors {
106
111
  display: inline;
107
112
  }
108
113
  div.field_with_errors label, .form-field-error, .form-errors {
109
114
  color: crimson;
110
115
  }
116
+
117
+ // New (Remote) Errors
118
+ p.form-error{
119
+ color: crimson;
120
+ font-size: 12px;
121
+ font-style: italic;
122
+ line-height: normal;
123
+ margin: 4px 0 0;
124
+ }
125
+ p.form-error-base{
126
+ margin: 0 0 10px;
127
+ }
128
+ @media (min-width: $screen-sm){
129
+ .form-horizontal .form-error-base{
130
+ margin-left: 17.3%;
131
+ }
132
+ }
111
133
  .date-select{
112
134
  &.form-control{
113
135
  display: inline-block;
@@ -4,9 +4,15 @@ class Admin::ApplicationController < Spud::ApplicationController
4
4
  add_breadcrumb "Dashboard", :admin_root_path
5
5
  layout 'admin/detail'
6
6
  respond_to :html, :json
7
+ # self.responder = TbCore::Responder
7
8
 
8
9
  private
9
10
 
11
+ def handle_request_error(error)
12
+ error.template = '/layouts/admin/error_page'
13
+ super(error)
14
+ end
15
+
10
16
  def require_admin_user
11
17
  if current_user.blank?
12
18
  flash[:notice] = "You must be logged in to access the requested page"
@@ -23,12 +23,8 @@ class Admin::UsersController < Admin::ApplicationController
23
23
  end
24
24
 
25
25
  def create
26
- @user = SpudUser.new(user_params)
27
- if @user.save
28
- render 'show', :status => 200
29
- else
30
- render 'new', :status => 422
31
- end
26
+ @user = SpudUser.create(user_params)
27
+ respond_with @user, :location => admin_users_path
32
28
  end
33
29
 
34
30
  def edit
@@ -48,12 +44,10 @@ class Admin::UsersController < Admin::ApplicationController
48
44
  private
49
45
 
50
46
  def load_user
51
- @user = SpudUser.where(:id => params[:id]).first
47
+ @user = SpudUser.find(params[:id])
52
48
  if @user.blank?
53
- flash[:error] = "User not found!"
54
- redirect_to admin_users_path and return false
49
+ raise Spud::NotFoundError.new('user')
55
50
  end
56
- return true
57
51
  end
58
52
 
59
53
  # attr_accessible :login,:email,:first_name,:last_name,:password,:password_confirmation,:password_salt,:last_login_at,:last_request_at,:last_login_ip,:failed_login_count,:current_login_at,:login_count,:persistence_token,:perishable_token,:single_access_token,:crypted_password, :current_login_ip, :created_at, :updated_at,:time_zone, :as => [:default, :admin]
@@ -6,12 +6,13 @@ class Spud::ApplicationController < ActionController::Base
6
6
  helper_method :current_user_session, :current_user, :current_user_id
7
7
  around_filter :set_time_zone
8
8
 
9
- include Spud::ApplicationHelper
9
+ include TbCore::ApplicationHelper
10
10
  before_action :set_mailer_default_url
11
11
 
12
12
  rescue_from Spud::RequestError, :with => :handle_request_error
13
+ rescue_from ActiveRecord::RecordNotFound, :with => :handle_record_not_found
13
14
  rescue_from ActionController::UnknownFormat, :with => :handle_unknown_format_error
14
-
15
+
15
16
  def not_found
16
17
  raise Spud::NotFoundError
17
18
  end
@@ -91,6 +92,11 @@ private
91
92
  end
92
93
  end
93
94
 
95
+ def handle_record_not_found(error)
96
+ error = Spud::NotFoundError.new('record')
97
+ handle_request_error(error)
98
+ end
99
+
94
100
  def handle_unknown_format_error(error)
95
101
  error = Spud::NotFoundError.new()
96
102
  handle_request_error(error)
@@ -21,7 +21,7 @@ class UserSessionsController < ApplicationController
21
21
  def destroy
22
22
  current_user_session.destroy unless current_user_session.blank?
23
23
  flash[:notice] = "Logout successful!"
24
- redirect_to login_path
24
+ redirect_back_or_default(login_path)
25
25
  end
26
26
 
27
27
  end