Commit 6c43d39c7ccec7028709ff36197cd564c8dc0fb0

Authored by Leonardo Merlin
2 parents 7a87daca 8a19bd73

Merge branch 'feature-proposal-vote'

src/app/components/article-service/article.service.js
... ... @@ -162,20 +162,15 @@
162 162 }
163 163 }
164 164  
165   - function voteProposal (proposal_id, params, cbSuccess, cbError){
  165 + function voteProposal (proposal_id, params){
166 166 var url = service.apiArticles + proposal_id + '/vote';
167 167 var paramsExtended = angular.extend({
168   - private_token: $rootScope.currentUser.private_token
169   - // private_token: 'e2198fdbcc20409f082829b4b5c0848e'
  168 + private_token: $rootScope.temporaryToken
170 169 }, params);
171 170  
172 171 var encodedParams = angular.element.param(paramsExtended);
173 172  
174   - UtilService.post(url, encodedParams).then(function(response){
175   - cbSuccess(response);
176   - }).catch(function(error){
177   - cbError(error);
178   - });
  173 + return UtilService.post(url, encodedParams);
179 174 }
180 175  
181 176 function getEvents (community_id, params) {
... ...
src/app/components/auth/auth.service.js
... ... @@ -53,11 +53,7 @@
53 53 }
54 54  
55 55 function activate (code) {
56   - var url = PATH.host +'/api/v1/activate';
57   - // var data = {
58   - // private_token: API.token,
59   - // activation_code: code
60   - // };
  56 + var url = PATH.host + '/api/v1/activate';
61 57 var encodedData = 'private_token=' + API.token;
62 58 encodedData += '&activation_code=' + code;
63 59  
... ... @@ -77,12 +73,7 @@
77 73 }
78 74  
79 75 function changePassword (code, newPassword, newPasswordConfirmation){
80   - var url = PATH.host +'/api/v1/new_password';
81   - // var data = {
82   - // code: code,
83   - // password: newPassword,
84   - // password_confirmation: newPasswordConfirmation
85   - // };
  76 + var url = PATH.host + '/api/v1/new_password';
86 77 var encodedData = 'code=' + code;
87 78 encodedData += '&password=' + newPassword;
88 79 encodedData += '&password_confirmation=' + newPasswordConfirmation;
... ... @@ -103,7 +94,7 @@
103 94 }
104 95  
105 96 function forgotPassword (data){
106   - var url = PATH.host +'/api/v1/forgot_password';
  97 + var url = PATH.host + '/api/v1/forgot_password';
107 98 var encodedData = ([
108 99 'value=' + data.login,
109 100 'captcha_text=' + data.captcha_text,
... ... @@ -145,9 +136,28 @@
145 136 }, function(response) {
146 137 $log.debug('AuthService.login [FAIL] response', response);
147 138 $rootScope.$broadcast(AUTH_EVENTS.loginFailed);
  139 +
  140 + return $q.reject(response);
148 141 });
149 142 }
150 143  
  144 + function loginCaptcha (data) {
  145 + var url = PATH.host + '/api/v1/login-captcha';
  146 + var encodedData = angular.element.param(data);
  147 +
  148 + return $http.post(url, encodedData).then(function(response){
  149 + // SUCCESS
  150 + $log.debug('AuthService.loginCaptcha [SUCCESS] response', response);
  151 +
  152 + var temporaryToken = response.data.private_token;
  153 + Session.setTemporaryToken(temporaryToken);
  154 + $rootScope.temporaryToken = temporaryToken;
  155 + return temporaryToken;
  156 + }, function(response){
  157 + return $q.reject(response.data);
  158 + });
  159 + }
  160 +
151 161 function logout () {
152 162  
153 163 Session.destroy();
... ... @@ -167,28 +177,13 @@
167 177 return (service.isAuthenticated() && authorizedRoles.indexOf(Session.userRole) !== -1);
168 178 }
169 179  
170   - // function _encodeObj(obj){
171   - // var result = [];
172   - // var str = null;
173   - // var p = null;
174   -
175   - // for (p in obj) {
176   - // if (obj.hasOwnProperty(p)) {
177   - // // str = encodeURIComponent(p) + '=' + obj[p];
178   - // str = p + '=' + obj[p];
179   - // result.push(str);
180   - // }
181   - // }
182   -
183   - // return result.join('&');
184   - // }
185   -
186 180 var service = {
187 181 register: register,
188 182 activate: activate,
189 183 changePassword: changePassword,
190 184 forgotPassword: forgotPassword,
191 185 login: login,
  186 + loginCaptcha: loginCaptcha,
192 187 logout: logout,
193 188 isAuthenticated: isAuthenticated,
194 189 isAuthorized: isAuthorized
... ... @@ -207,7 +202,7 @@
207 202  
208 203 service.create = function(data) {
209 204  
210   - $localStorage.currentUser = data;
  205 + $localStorage.currentUser = data.user;
211 206 $log.debug('User session created.', $localStorage.currentUser);
212 207  
213 208 return $localStorage.currentUser;
... ... @@ -224,6 +219,14 @@
224 219 return $localStorage.currentUser;
225 220 };
226 221  
  222 + service.setTemporaryToken = function (private_token) {
  223 + $localStorage.temporaryToken = private_token;
  224 + };
  225 +
  226 + service.getTemporaryToken = function () {
  227 + return $localStorage.temporaryToken;
  228 + };
  229 +
227 230 return service;
228 231 }
229 232  
... ...
src/app/components/proposal-box/proposal-box.directive.js
... ... @@ -9,30 +9,34 @@
9 9 function proposalBox() {
10 10  
11 11 /** @ngInject */
12   - function ProposalBoxController($scope, $rootScope, $state, VOTE_STATUS, VOTE_OPTIONS, $log) {
  12 + function ProposalBoxController($scope, $rootScope, $state, $timeout, $interval, $window, VOTE_STATUS, VOTE_OPTIONS, AuthService, DialogaService, $log) {
13 13 $log.debug('ProposalBoxController');
14 14  
15 15 var vm = this;
16 16 vm.$scope = $scope;
17 17 vm.$rootScope = $rootScope;
18 18 vm.$state = $state;
  19 + vm.$timeout = $timeout;
  20 + vm.$interval = $interval;
  21 + vm.$window = $window;
19 22 vm.VOTE_STATUS = VOTE_STATUS;
20 23 vm.VOTE_OPTIONS = VOTE_OPTIONS;
  24 + vm.AuthService = AuthService;
21 25 vm.$log = $log;
22 26  
23 27 vm.init();
24 28 vm.addListeners();
25 29 }
26 30  
27   - ProposalBoxController.prototype.init = function () {
  31 + ProposalBoxController.prototype.init = function() {
28 32  
29 33 var vm = this;
30 34  
31   - vm.canVote = vm.canVote || false;
  35 + vm.showVote = vm.showVote || false;
32 36 vm.focus = vm.focus || false;
33 37 vm.STATE = null;
34 38 vm.errorOnSkip = false;
35   - vm.showAuthMessage = null;
  39 + vm.showCaptchaForm = null;
36 40 vm.voteProposalRedirectURI = null;
37 41  
38 42 var slug = vm.topic.slug;
... ... @@ -40,37 +44,52 @@
40 44 vm.voteProposalRedirectURI = 'state=programa&task=vote-proposal&slug=' + slug + '&proposal_id=' + proposal_id;
41 45 };
42 46  
43   - ProposalBoxController.prototype.addListeners = function () {
  47 + ProposalBoxController.prototype.addListeners = function() {
44 48 var vm = this;
45 49  
46   - vm.$scope.$on('proposal-box:proposal-loaded', function(event, data){
47   - if(data.success){
  50 + vm.$scope.$on('proposal-box:proposal-loaded', function(event, data) {
  51 + if (data.success) {
48 52 vm.STATE = null;
49 53 }
50 54  
51   - if(data.error){
  55 + if (data.error) {
52 56 vm.errorOnSkip = data.error;
53 57 }
54 58 });
55 59  
56   - vm.$scope.$on('proposal-box:vote-response', function(event, data){
57   - vm.$log.debug('proposal-box:vote-response');
58   - vm.$log.debug('event', event);
59   - vm.$log.debug('data', data);
60   -
61   - if(data.success) {
  60 + vm.$scope.$on('proposal-box:vote-response', function(event, data) {
  61 + if (data.success) {
62 62 vm.STATE = vm.VOTE_STATUS.SUCCESS;
63 63 }
64 64  
65   - if(data.error) {
  65 + if (data.error) {
66 66 vm.STATE = vm.VOTE_STATUS.ERROR;
67 67 }
68 68  
69   - vm.message = data.message;
  69 + if (data.code === 401) {
  70 + vm.message = 'Não autorizado.';
  71 + }
  72 +
  73 + vm.messageCode = data.code;
70 74 });
  75 +
  76 + // Load captcha
  77 + var stop = null;
  78 + stop = vm.$interval(function() {
  79 + var $el = angular.element('#serpro_captcha');
  80 +
  81 + if ($el && $el.length > 0) {
  82 + vm.$window.initCaptcha($el[0]);
  83 + vm.$interval.cancel(stop);
  84 + stop = undefined;
  85 + }else {
  86 + vm.$log.debug('captcha element not found.');
  87 + }
  88 +
  89 + }, 10);
71 90 };
72 91  
73   - ProposalBoxController.prototype.showContent = function (slug) {
  92 + ProposalBoxController.prototype.showContent = function(slug) {
74 93 var vm = this;
75 94  
76 95 vm.$state.go('programa', {
... ... @@ -81,45 +100,106 @@
81 100 });
82 101 };
83 102  
84   - ProposalBoxController.prototype.vote = function (value) {
  103 + ProposalBoxController.prototype.canVote = function() {
85 104 var vm = this;
86 105  
87   - if(vm.$rootScope.currentUser){
88   - vm.$scope.$emit('proposal-box:vote', {
89   - OPTION: value,
90   - proposal_id: vm.proposal.id
91   - });
92   - vm.$log.debug('Sending vote', value);
93   - }else{
94   - vm.$log.info('Must be logged in...');
95   - vm.showAuthMessage = true;
96   - }
  106 + return !!vm.$rootScope.temporaryToken;
97 107 };
98 108  
99   - ProposalBoxController.prototype.voteDown = function () {
  109 + ProposalBoxController.prototype.submitCaptcha = function($event, captchaForm) {
100 110 var vm = this;
101 111  
102   - vm.STATE = vm.VOTE_STATUS.LOADING;
103   - vm.$scope.$emit('proposal-box:vote', {
104   - OPTION: vm.VOTE_OPTIONS.DOWN,
105   - proposal_id: vm.proposal.id
  112 + var target = $event.target;
  113 + var $target = angular.element(target);
  114 + var $captcha = $target.find('[name="txtToken_captcha_serpro_gov_br"]');
  115 +
  116 + vm.sendingCaptcha = true;
  117 + vm.AuthService.loginCaptcha({
  118 + captcha_text: captchaForm.captcha_text.$modelValue,
  119 + txtToken_captcha_serpro_gov_br: $captcha.val()
  120 + }).then(function(data) {
  121 + // SUCCESS
  122 + vm.$log.debug('register success.data', data);
  123 +
  124 + // SEND VOTE
  125 + if (vm._oldVoteValue) {
  126 + vm.vote(vm._oldVoteValue);
  127 + vm._oldVoteValue = null;
  128 + }
  129 + // hide captcha form
  130 + vm.showCaptchaForm = false;
  131 +
  132 + }, function(data) {
  133 + // ERROR
  134 + vm.$log.debug('register error.data', data);
  135 +
  136 + vm.sendingCaptchaError = {
  137 + code: data.status,
  138 + message: data.message || ('Erro (' + data.status + '). Já estamos trabalhando para resolver o problema.<br/>Por favor, tente novamente mais tarde')
  139 + };
  140 +
  141 + if (angular.equals(vm.sendingCaptchaError.message, 'Internal captcha validation error')) {
  142 + vm.sendingCaptchaError.message = 'Erro interno ao tentar validar captcha.<br/><br/>Já estamos trabalhando para resolver o problema.<br/>Por favor, tente novamente mais tarde.';
  143 + }
  144 +
  145 + }, function(data) {
  146 + // UPDATE
  147 + vm.$log.debug('register update.data', data);
  148 + }).finally(function() {
  149 + vm.sendingCaptcha = false;
106 150 });
107   - vm.$log.debug('Sending vote');
108 151 };
109 152  
110   - ProposalBoxController.prototype.skip = function () {
  153 + ProposalBoxController.prototype.captchaTryAgain = function() {
  154 + var vm = this;
  155 +
  156 + vm.showCaptchaForm = true;
  157 + vm.sendingCaptcha = false;
  158 + vm.sendingCaptchaError = false;
  159 + vm.message = null;
  160 +
  161 + // reload new captcha
  162 + var $el = angular.element('#serpro_captcha');
  163 + vm.$window.reloadCaptcha($el[0]);
  164 +
  165 + // focus on input
  166 + angular.element('#captcha_text').val('').focus();
  167 + };
  168 +
  169 + ProposalBoxController.prototype.vote = function(value) {
  170 + var vm = this;
  171 +
  172 + vm._oldVoteValue = value;
  173 + if (vm.canVote()) {
  174 + if (vm.doVote) {
  175 + vm.doVote({
  176 + proposal_id: vm.proposal.id,
  177 + value: value
  178 + });
  179 + }else {
  180 + vm.$log.error('No vote function to handler votes');
  181 + }
  182 + }else {
  183 + vm.$log.debug('You cannot vote.');
  184 + vm.showCaptchaForm = true;
  185 +
  186 + angular.element('#captcha_text').focus();
  187 + }
  188 + };
  189 +
  190 + ProposalBoxController.prototype.skip = function() {
111 191 var vm = this;
112 192  
113 193 vm.errorOnSkip = false;
114 194 vm.STATE = vm.VOTE_STATUS.LOADING;
115   - vm.$scope.$emit('proposal-box:vote', {
116   - OPTION: vm.VOTE_OPTIONS.SKIP,
117   - proposal_id: vm.proposal.id
  195 + vm.doVote({
  196 + proposal_id: vm.proposal.id,
  197 + value: vm.VOTE_OPTIONS.SKIP
118 198 });
119 199 vm.$log.debug('Sending vote');
120 200 };
121 201  
122   - ProposalBoxController.prototype.getSocialUrl = function () {
  202 + ProposalBoxController.prototype.getSocialUrl = function() {
123 203 var vm = this;
124 204  
125 205 return vm.$state.href('programa', {
... ... @@ -135,8 +215,9 @@
135 215 proposal: '=',
136 216 topic: '=',
137 217 category: '=',
138   - canVote: '=',
139   - focus: '@'
  218 + showVote: '=',
  219 + focus: '@',
  220 + doVote: '&'
140 221 // @ -> Text binding / one-way binding
141 222 // = -> Direct model binding / two-way binding
142 223 // & -> Behaviour binding / Method binding
... ...
src/app/components/proposal-box/proposal-box.html
... ... @@ -52,8 +52,10 @@
52 52 </div>
53 53 <div class="feedback" ng-if="vm.STATE === vm.VOTE_STATUS.ERROR">
54 54 <p class="feedback--title">Erro!</p>
55   - <p class="feedback--message" ng-if="vm.message">
56   - Motivo: {{vm.message}}
  55 + <p class="feedback--message" ng-if="vm.messageCode === 401">
  56 + Não autorizado. Insira um novo captcha.
  57 + <br>
  58 + <button type="button" class="btn btn-link" ng-click="vm.captchaTryAgain()">Gerar novo captcha</button>
57 59 </p>
58 60 </div>
59 61 </div>
... ... @@ -65,18 +67,48 @@
65 67 </div>
66 68 </div>
67 69 </div>
68   - <div ng-show="vm.showAuthMessage">
  70 + <div ng-show="vm.showCaptchaForm">
69 71 <div class="proposal-message-panel">
70 72 <div class="row">
71 73 <div class="row-height">
72 74 <div class="col-sm-12 col-height col-middle">
73 75 <div class="inside inside-full-height">
74 76 <div class="content text-center">
75   - <p>Você precisa estar logado para votar na proposta</p>
76   - <br>
77   - <p>
78   - <a ui-sref="entrar({redirect_uri: vm.voteProposalRedirectURI})">Clique aqui para ir para a página de login</a>
79   - </p>
  77 + <div ng-show="vm.sendingCaptcha">
  78 + <p>Enviando captcha...</p>
  79 + </div>
  80 + <div ng-hide="vm.sendingCaptcha">
  81 + <div class="row feedback-message" ng-show="vm.sendingCaptchaError">
  82 + <div class="col-sm-12">
  83 + <div class="feedback--title alert alert-danger">Erro!</div>
  84 + <div class="feedback--message" ng-if="!vm.message">
  85 + <p ng-bind-html="vm.sendingCaptchaError.message"></p>
  86 + </div>
  87 + <div>
  88 + <button type="reset" class="btn btn-link" ng-click="vm.captchaTryAgain()">Tentar novamente</button>
  89 + <button type="reset" class="btn btn-link" ng-click="vm.showCaptchaForm = false">Voltar</button>
  90 + </div>
  91 + </div>
  92 + </div>
  93 + <div ng-hide="vm.sendingCaptchaError">
  94 + <form name="captchaForm" ng-submit="vm.submitCaptcha($event, captchaForm)">
  95 + <div class="form-group">
  96 + <div id="serpro_captcha" class="captcha"></div>
  97 + <div class="captcha">Digite os caracteres acima:</div>
  98 + <div class="captcha">
  99 + <input type="text" name="captcha_text" id="captcha_text" aria-label="Escreva os caracteres do captcha aqui" ng-model="vm._captcha_text" ng-minlength="" ng-maxlength="" required>
  100 + <validation-messages field="captchaForm.captcha_text"></validation-messages>
  101 + </div>
  102 + </div>
  103 + <div class="form-group">
  104 + <button type="submit" class="btn btn-lg btn-block btn-submit">Enviar</button>
  105 + </div>
  106 + <div class="form-group">
  107 + <button type="reset" class="btn btn-link" ng-click="vm.showCaptchaForm = false">Voltar</button>
  108 + </div>
  109 + </form>
  110 + </div>
  111 + </div>
80 112 </div>
81 113 </div>
82 114 </div>
... ... @@ -112,12 +144,12 @@
112 144 <div class="proposal-box--content">
113 145 <div class="proposal-box--content-inner">{{vm.proposal.abstract}}</div>
114 146 </div>
115   - <div ng-hide="vm.canVote" class="proposal-box--join">
  147 + <div ng-hide="vm.showVote" class="proposal-box--join">
116 148 <button class="btn btn-link color-theme-common-fg" ng-click="vm.showContent(vm.topic.slug)">
117 149 Participe
118 150 </button>
119 151 </div>
120   - <div ng-show="vm.canVote" class="proposal-box--actions text-center">
  152 + <div ng-show="vm.showVote" class="proposal-box--actions text-center">
121 153 <div class="row">
122 154 <div class="col-xs-4">
123 155 <div class="action vote_for" ng-click="vm.vote(vm.VOTE_OPTIONS.UP)">
... ...
src/app/components/proposal-box/proposal-box.scss
... ... @@ -91,9 +91,15 @@
91 91 .content {
92 92 color: #262626;
93 93 font-size: 24px;
  94 + font-size: 2.4rem;
94 95 font-weight: bold;
95 96 line-height: 24px;
96 97 padding: 10px 30px;
  98 +
  99 + form {
  100 + font-size: 18px;
  101 + font-size: 1.8rem;
  102 + }
97 103 }
98 104  
99 105 .message-icon {
... ... @@ -108,11 +114,13 @@
108 114  
109 115 &--title {
110 116 font-size: 22px;
  117 + font-size: 2.2rem;
111 118 font-weight: bold;
112 119 }
113 120  
114 121 &--message {
115 122 font-size: 14px;
  123 + font-size: 1.4rem;
116 124 font-weight: normal;
117 125 line-height: 20px;
118 126 margin-top: 48px;
... ...
src/app/components/proposal-grid/proposal-grid.html
1 1 <div class="proposal-grid row">
2 2 <div ng-repeat="proposal in vm.proposals as results">
3   - <proposal-box proposal="proposal" topic="proposal.parent" category="proposal.parent.categories[0]" class="col-xs-12 col-sm-6"></proposal-box>
  3 + <proposal-box proposal="proposal" topic="proposal.parent" category="proposal.parent.categories[0]" show-vote="false" class="col-xs-12 col-sm-6"></proposal-box>
4 4 <div ng-if="$odd" class="clearfix"></div>
5 5 </div>
6 6 <div class="animate-repeat" ng-if="results.length == 0">
... ...
src/app/components/proposal-list/proposal-list.directive.js
... ... @@ -31,7 +31,7 @@
31 31  
32 32 vm.$timeout(function(){
33 33 attachPopover.call(vm);
34   - }, 1000);
  34 + }, 0);
35 35 };
36 36  
37 37 ProposalListController.prototype.showContent = function (proposal) {
... ...
src/app/index.run.js
... ... @@ -41,6 +41,7 @@
41 41 });
42 42  
43 43 $rootScope.currentUser = $localStorage.currentUser;
  44 + $rootScope.temporaryToken = $localStorage.temporaryToken;
44 45  
45 46 $log.debug('[RUN] Auth end.');
46 47 }
... ...
src/app/layout.scss
... ... @@ -9,7 +9,6 @@
9 9 display: table-cell;
10 10 float: none;
11 11 height: 100%;
12   - background-color: #181e21;
13 12 }
14 13  
15 14 .col-top {
... ... @@ -206,6 +205,13 @@
206 205 margin-left: 10px;
207 206 padding: 0;
208 207 border-radius: 100%;
  208 +
  209 + &:hover,
  210 + &:focus,
  211 + &:active {
  212 + color: #fff;
  213 + border-color: #fff;
  214 + }
209 215 }
210 216  
211 217 ul.list-color {
... ... @@ -224,7 +230,7 @@ ul.list-color li:before {
224 230 }
225 231  
226 232 ul.list-color li:before {
227   - content: "";
  233 + content: "\2022";
228 234 font-weight: bold;
229 235 font-size: 20px;
230 236 position: relative;
... ... @@ -333,7 +339,7 @@ blockquote {
333 339 position: relative;
334 340 margin: 0px;
335 341 border-left: none;
336   - // line-height: 28px;
  342 +
337 343 &:before {
338 344 content: "\231C";
339 345 font-size: 300px;
... ...
src/app/pages/programas/programa.controller.js
... ... @@ -82,7 +82,10 @@
82 82 }
83 83  
84 84 vm.loadingTopProposals = true;
85   - vm.DialogaService.getProposalsByTopicId(vm.article.id, {}, function(data) {
  85 + vm.DialogaService.getProposalsByTopicId(vm.article.id, {
  86 + 'limit': 5
  87 + }, function(data) {
  88 + vm.total_proposals = parseInt(data._obj.headers('total'));
86 89 vm.proposals = data.articles;
87 90 vm.proposalsTopFive = vm.proposals.slice(0, 5);
88 91 vm.proposalsTopRated = vm.proposals.slice(0, 3);
... ... @@ -142,24 +145,6 @@
142 145 vm.proposalStatus = vm.PROPOSAL_STATUS.ERROR;
143 146 });
144 147 });
145   -
146   - vm.$scope.$on('proposal-box:vote', function(event, params) {
147   - // vm.$log.debug('event', event);
148   - // vm.$log.debug('params', params);
149   - var proposal_id = params.proposal_id;
150   - var OPTION = params.OPTION;
151   -
152   - switch (OPTION){
153   - case vm.VOTE_OPTIONS.UP:
154   - case vm.VOTE_OPTIONS.DOWN:
155   - case vm.VOTE_OPTIONS.SKIP:
156   - vm.vote(proposal_id, OPTION);
157   - break;
158   - default:
159   - vm.$log.error('Vote option not handled:', OPTION);
160   - break;
161   - }
162   - });
163 148 };
164 149  
165 150 ProgramaPageController.prototype.loadProposalById = function(proposal_id) {
... ... @@ -219,31 +204,28 @@
219 204 return;
220 205 }
221 206  
222   - if (!vm.$rootScope.currentUser) {
223   - // vm.$state.go('entrar', {
224   - // redirect_uri: vm.sendProposalRedirectURI,
225   - // message: 'Você precisa estar logado para votar em uma proposta.'
226   - // }, {
227   - // location: true
228   - // });
  207 + if (!vm.$rootScope.temporaryToken) {
  208 + vm.$log.debug('"temporaryToken" not defined. Abort.');
229 209 return;
230 210 }
231 211  
232 212 vm.DialogaService.voteProposal(proposal_id, {
233 213 value: value
234   - }, function(response) {
235   - vm.$log.debug('response', response);
  214 + }).then(function(response) {
  215 + vm.$log.debug('voteProposal response', response);
236 216  
237 217 response.success = true;
238 218 vm.$scope.$broadcast('proposal-box:vote-response', response);
239   - }, function(error) {
240   - vm.$log.error('error', error);
  219 + }, function(response) {
  220 + vm.$log.debug('voteProposal error', response);
  221 +
  222 + response.error = true;
  223 + vm.$scope.$broadcast('proposal-box:vote-response', response);
  224 + }).finally(function(response){
  225 + vm.$log.debug('voteProposal finally', response);
241 226  
242   - error.error = true;
243   - vm.$scope.$broadcast('proposal-box:vote-response', error);
244 227 });
245 228 };
246   - ProgramaPageController.prototype.voteHasBeenComputed = function() {};
247 229  
248 230 ProgramaPageController.prototype.showProposalsList = function() {
249 231 var vm = this;
... ...
src/app/pages/programas/programa.html
... ... @@ -90,7 +90,14 @@
90 90 <div>
91 91 <div class="col-xs-12" ng-if="!pagePrograma.loadingProposalBox && pagePrograma.randomProposal" ng-class="{'focused-proposal': !!pagePrograma.search.proposal_id}">
92 92 <h3 class="color-theme-fg">Apoie outras propostas</h3>
93   - <proposal-box proposal="pagePrograma.randomProposal" topic="pagePrograma.article" category="pagePrograma.category" can-vote="true" focus="{{pagePrograma.search.proposal_id}}" ></proposal-box>
  93 + <proposal-box
  94 + proposal="pagePrograma.randomProposal"
  95 + topic="pagePrograma.article"
  96 + category="pagePrograma.category"
  97 + show-vote="true"
  98 + focus="{{pagePrograma.search.proposal_id}}"
  99 + do-vote="pagePrograma.vote(proposal_id, value)"
  100 + ></proposal-box>
94 101 </div>
95 102  
96 103 <!-- Loading Proposal Box -->
... ... @@ -133,7 +140,7 @@
133 140 <div class="row">
134 141 <div class="col-xs-12">
135 142 <a ui-sref="ranking({tema: pagePrograma.category.slug, programa: pagePrograma.article.slug})" class="btn btn-link">
136   - <span ng-if="pagePrograma.proposals.length > 1">Veja todas as {{pagePrograma.proposals.length}} propostas</span>
  143 + <span ng-if="pagePrograma.proposals.length > 1">Veja todas as {{pagePrograma.total_proposals}} propostas</span>
137 144 <span ng-if="pagePrograma.proposals.length === 1">Ir para a página de ranking</span>
138 145 </a>
139 146 </div>
... ...
src/app/pages/programas/programas.controller.js
... ... @@ -139,6 +139,21 @@
139 139  
140 140 };
141 141  
  142 + ProgramasPageController.prototype.submitSearch = function() {
  143 + var vm = this;
  144 +
  145 + vm.loadingFilter = true;
  146 +
  147 + // scroll to result grid
  148 + var $searchResult = angular.element('#search-result');
  149 + if ($searchResult && $searchResult.length > 0) {
  150 + angular.element('body').animate({scrollTop: $searchResult.offset().top}, 'fast');
  151 + vm.filtredPrograms = vm.getFiltredPrograms();
  152 + }else {
  153 + vm.$log.warn('#search-result element not found.');
  154 + }
  155 + };
  156 +
142 157 ProgramasPageController.prototype.showAllPrograms = function($event) {
143 158 var vm = this;
144 159 $event.stopPropagation();
... ...
src/app/pages/programas/programas.html
... ... @@ -26,7 +26,7 @@
26 26 <label for="articleQueryFilter" class="control-label sr-only">Buscar programas:</label>
27 27 <input id="articleQueryFilter" type="search" class="form-control input-search" ng-model="pageProgramas.query" placeholder="Buscar programas" aria-label="Buscar programas" >
28 28 <span class="input-group-btn">
29   - <button type="button" class="btn btn-default" ng-click="pageProgramas.search()">
  29 + <button type="button" class="btn btn-default" ng-click="pageProgramas.submitSearch()">
30 30 <span class="icon-circle icon-small color-theme-common-bg">
31 31 <span class="glyphicon glyphicon-search"></span>
32 32 </span>
... ... @@ -58,7 +58,7 @@
58 58 <label for="articleQueryFilter" class="control-label sr-only">Buscar programas:</label>
59 59 <input id="articleQueryFilter" type="search" class="form-control input-search" ng-model="pageProgramas.query" placeholder="Buscar programas" aria-label="Buscar programas" >
60 60 <span class="input-group-btn">
61   - <button type="button" class="btn btn-default" ng-click="pageProgramas.search()">
  61 + <button type="button" class="btn btn-default" ng-click="pageProgramas.submitSearch()">
62 62 <span class="icon-circle icon-small color-theme-common-bg">
63 63 <span class="glyphicon glyphicon-search"></span>
64 64 </span>
... ... @@ -68,7 +68,7 @@
68 68 </div>
69 69 </div>
70 70 </div>
71   - <div class="row">
  71 + <div id="search-result" class="row">
72 72 <div class="col-sm-12">
73 73 <header class="header">
74 74 <h2>Conheça os programas</h2>
... ...
src/app/pages/propostas/propostas.controller.js
... ... @@ -6,11 +6,12 @@
6 6 .controller('PropostasPageController', PropostasPageController);
7 7  
8 8 /** @ngInject */
9   - function PropostasPageController(DialogaService, $scope, $location, $filter, $log) {
  9 + function PropostasPageController(DialogaService, $scope, $rootScope, $location, $filter, $log) {
10 10 var vm = this;
11 11  
12 12 vm.DialogaService = DialogaService;
13 13 vm.$scope = $scope;
  14 + vm.$rootScope = $rootScope;
14 15 vm.$location = $location;
15 16 vm.$filter = $filter;
16 17 vm.$log = $log;
... ... @@ -18,6 +19,7 @@
18 19 vm.init();
19 20 vm.loadData();
20 21 // vm.attachListeners(); // attach listeners after load data (SYNC)
  22 + vm.$rootScope.focusMainContent();
21 23  
22 24 $log.debug('PropostasPageController');
23 25 }
... ... @@ -156,4 +158,19 @@
156 158 return output;
157 159 };
158 160  
  161 + PropostasPageController.prototype.submitSearch = function() {
  162 + var vm = this;
  163 +
  164 + vm.loadingFilter = true;
  165 +
  166 + // scroll to result grid
  167 + var $searchResult = angular.element('#search-result');
  168 + if ($searchResult && $searchResult.length > 0) {
  169 + angular.element('body').animate({scrollTop: $searchResult.offset().top}, 'fast');
  170 + vm.filtredProposals = vm.getFiltredProposals();
  171 + }else {
  172 + vm.$log.warn('#search-result element not found.');
  173 + }
  174 + };
  175 +
159 176 })();
... ...
src/app/pages/propostas/propostas.html
... ... @@ -6,7 +6,7 @@
6 6 </div>
7 7 </div>
8 8  
9   -<div class="page--propostas">
  9 +<div class="page--propostas" role="main">
10 10  
11 11 <section class="section-info" ng-if="pagePropostas.loading || pagePropostas.error">
12 12 <div class="container">
... ... @@ -46,7 +46,7 @@
46 46 <label for="articleQueryFilter" class="control-label sr-only">Buscar propostas:</label>
47 47 <input id="articleQueryFilter" type="search" class="form-control input-search" ng-model="pagePropostas.query" placeholder="Buscar propostas" aria-label="Buscar propostas" >
48 48 <span class="input-group-btn">
49   - <button type="button" class="btn btn-default" ng-click="pagePropostas.search()">
  49 + <button type="button" class="btn btn-default" ng-click="pagePropostas.submitSearch()">
50 50 <span class="icon-circle icon-small color-theme-common-bg">
51 51 <span class="glyphicon glyphicon-search"></span>
52 52 </span>
... ... @@ -78,7 +78,7 @@
78 78 <label for="articleQueryFilter" class="control-label sr-only">Buscar propostas:</label>
79 79 <input id="articleQueryFilter" type="search" class="form-control input-search" ng-model="pagePropostas.query" placeholder="Buscar propostas" aria-label="Buscar propostas" >
80 80 <span class="input-group-btn">
81   - <button type="button" class="btn btn-default" ng-click="pagePropostas.search()">
  81 + <button type="button" class="btn btn-default" ng-click="pagePropostas.submitSearch()">
82 82 <span class="icon-circle icon-small color-theme-common-bg">
83 83 <span class="glyphicon glyphicon-search"></span>
84 84 </span>
... ... @@ -89,7 +89,7 @@
89 89 </div>
90 90 </div>
91 91  
92   - <div class="row" ng-if="pagePropostas.proposals">
  92 + <div id="search-result" class="row" ng-if="pagePropostas.proposals">
93 93 <div class="col-sm-12">
94 94 <header class="header">
95 95 <h2>Total de Propostas: "<b>{{pagePropostas.filtredProposals.length}} propostas</b>"</h2>
... ...