Commit 9d6d9600e7c52f7db1401daf723547f347686adc

Authored by Leonardo Merlin
2 parents 9589c679 0c7d6a01

Merge branch 'refact-content' into merlin

Showing 32 changed files with 741 additions and 115 deletions   Show diff stats
1 # Dialoga App 1 # Dialoga App
2 2
  3 +Start development:
  4 +
  5 +```bash
  6 +# dev with no proxy (local data)
  7 +gulp serve
  8 +json-server data.js -p 9000 -w data.js
  9 +
  10 +# dev with proxy to hom server
  11 +gulp serve --target="http://hom.login.dialoga.gov.br"
  12 +
  13 +# dev with proxy to production server
  14 +gulp serve --target="http://login.dialoga.gov.br"
  15 +```
  16 +
3 # Project Decisions 17 # Project Decisions
4 18
5 - [generator-gulp-angular](https://github.com/Swiip/generator-gulp-angular) 19 - [generator-gulp-angular](https://github.com/Swiip/generator-gulp-angular)
@@ -2,19 +2,20 @@ @@ -2,19 +2,20 @@
2 "name": "dialoga", 2 "name": "dialoga",
3 "version": "0.0.0", 3 "version": "0.0.0",
4 "dependencies": { 4 "dependencies": {
  5 + "angular": "~1.4.0",
5 "angular-animate": "~1.4.0", 6 "angular-animate": "~1.4.0",
  7 + "angular-breadcrumb": "~0.4.1",
6 "angular-cookies": "~1.4.0", 8 "angular-cookies": "~1.4.0",
7 - "angular-touch": "~1.4.0",  
8 "angular-sanitize": "~1.4.0", 9 "angular-sanitize": "~1.4.0",
  10 + "angular-slugify": "~1.0.1",
  11 + "angular-social-links": "~0.0.19",
  12 + "angular-touch": "~1.4.0",
9 "angular-ui-router": "~0.2.15", 13 "angular-ui-router": "~0.2.15",
10 - "jquery": "~2.1.4",  
11 - "bootstrap-sass-official": "~3.3.4",  
12 "animate.css": "~3.3.0", 14 "animate.css": "~3.3.0",
13 - "angular": "~1.4.0", 15 + "bootstrap-sass-official": "~3.3.4",
  16 + "jquery": "~2.1.4",
14 "modernizr": "~2.8.3", 17 "modernizr": "~2.8.3",
15 - "angular-slugify": "~1.0.1",  
16 - "open-sans-fontface": "~1.4.2",  
17 - "angular-social-links": "~0.0.19" 18 + "open-sans-fontface": "~1.4.2"
18 }, 19 },
19 "devDependencies": { 20 "devDependencies": {
20 "angular-mocks": "~1.4.0" 21 "angular-mocks": "~1.4.0"
src/app/components/a11y-bar/a11y-bar.html
@@ -4,25 +4,25 @@ @@ -4,25 +4,25 @@
4 <div class="col-sm-6"> 4 <div class="col-sm-6">
5 <ul class="skip-links list-inline list-unstyled"> 5 <ul class="skip-links list-inline list-unstyled">
6 <li> 6 <li>
7 - <a accesskey="1" href="#content" id="skip-to-content" ng-click="skipToContent($event)"> 7 + <a accesskey="1" href="#content" id="skip-to-content" ng-click="focusOn('#content', $event)">
8 Ir para o conteúdo 8 Ir para o conteúdo
9 <span>1</span> 9 <span>1</span>
10 </a> 10 </a>
11 </li> 11 </li>
12 <li> 12 <li>
13 - <a accesskey="2" href="#navigation" id="skip-to-navigation" ng-click="skipToNavigation($event)"> 13 + <a accesskey="2" href="#navigation" id="skip-to-navigation" ng-click="focusOn('#navigation', $event)">
14 Ir para o menu 14 Ir para o menu
15 <span>2</span> 15 <span>2</span>
16 </a> 16 </a>
17 </li> 17 </li>
18 <li> 18 <li>
19 - <a accesskey="3" href="#search" id="skip-to-search" ng-click="skipToSearch($event)"> 19 + <a accesskey="3" href="#search" id="skip-to-search" ng-click="focusOn('#search', $event)">
20 Ir para a busca 20 Ir para a busca
21 <span>3</span> 21 <span>3</span>
22 </a> 22 </a>
23 </li> 23 </li>
24 <li> 24 <li>
25 - <a accesskey="4" href="#footer" id="skip-to-footer" ng-click="skipToFooter($event)"> 25 + <a accesskey="4" href="#footer" id="skip-to-footer" ng-click="focusOn('#footer', $event)">
26 Ir para o rodapé 26 Ir para o rodapé
27 <span>4</span> 27 <span>4</span>
28 </a> 28 </a>
src/app/components/article-box/article-box.directive.js
@@ -49,16 +49,6 @@ @@ -49,16 +49,6 @@
49 ArticleBoxController.prototype.showContent = function () { 49 ArticleBoxController.prototype.showContent = function () {
50 var vm = this; 50 var vm = this;
51 51
52 - vm.$state.go('programa-conheca', {  
53 - slug: vm.article.slug  
54 - }, {  
55 - location: true  
56 - });  
57 - };  
58 -  
59 - ArticleBoxController.prototype.showPreview = function () {  
60 - var vm = this;  
61 -  
62 vm.$state.go('programa-conteudo', { 52 vm.$state.go('programa-conteudo', {
63 slug: vm.article.slug 53 slug: vm.article.slug
64 }, { 54 }, {
src/app/components/article-box/article-box.html
1 -<article class="article-box" ng-click="vm.showPreview()" ng-class="vm.category.slug"> 1 +<article class="article-box" ng-click="vm.showContent()" ng-class="vm.category.slug">
2 <div> 2 <div>
3 <h2 class="article-box--category">{{vm.category.name}}</h2> 3 <h2 class="article-box--category">{{vm.category.name}}</h2>
4 <div class="article-box--image-wrapper"> 4 <div class="article-box--image-wrapper">
src/app/components/article-box/article-box.scss
@@ -129,6 +129,10 @@ $article-box-space: 20px; @@ -129,6 +129,10 @@ $article-box-space: 20px;
129 transition: all $time ease-in-out; 129 transition: all $time ease-in-out;
130 } 130 }
131 131
  132 + .button--themed {
  133 + padding: 20px;
  134 + }
  135 +
132 &:hover { 136 &:hover {
133 background-color: #d9d9d9; 137 background-color: #d9d9d9;
134 138
src/app/components/article-content/article-content.directive.js
src/app/components/article-content/article-content.scss
@@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
1 -.article-content {  
2 - h2 {  
3 - font-size: 38px;  
4 - font-weight: 500;  
5 - margin-bottom: 40px;  
6 - padding-bottom: 20px;  
7 -  
8 - small {  
9 - display: block;  
10 - font-size: 16px;  
11 - padding-top: 5px;  
12 - text-transform: none;  
13 - }  
14 - }  
15 -}  
src/app/components/article-grid/article-grid.html
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <header class="header"> 2 <header class="header">
3 <h2>Conheça os programas <span class="small">({{vm.filtredArticleList.length}}/{{vm.articles.length}})</span></h2> 3 <h2>Conheça os programas <span class="small">({{vm.filtredArticleList.length}}/{{vm.articles.length}})</span></h2>
4 <button type="button" class="btn btn-link" ng-click="vm.showAll($event)"> 4 <button type="button" class="btn btn-link" ng-click="vm.showAll($event)">
5 - <span class="glyphicon glyphicon-chevron-right"></span> Ver todos os {{vm.articles.length}} programas 5 + <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> Ver todos os {{vm.articles.length}} programas
6 </button> 6 </button>
7 </header> 7 </header>
8 <div> 8 <div>
src/app/components/breadcrumb/breadcrumb.scss 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +.custom-breadcrumb {
  2 + .breadcrumb > li + li:before {
  3 + content: '>';
  4 + }
  5 +}
src/app/components/breadcrumb/template.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<div class="custom-breadcrumb">
  2 + <ol class="breadcrumb">
  3 + <li ng-repeat="step in steps | limitTo:(steps.length-1)">
  4 + <a href="{{step.ncyBreadcrumbLink}}" ng-bind-html="step.ncyBreadcrumbLabel"></a>
  5 + </li>
  6 + <li ng-repeat="step in steps | limitTo:-1" class="active">
  7 + <span ng-bind-html="step.ncyBreadcrumbLabel"></span>
  8 + </li>
  9 + </ol>
  10 +</div>
src/app/components/category-list/category-list.scss
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 top: 10px; 37 top: 10px;
38 left: 10px; 38 left: 10px;
39 // border: 1px solid #fff; 39 // border: 1px solid #fff;
40 - border-radius: 48px; 40 + border-radius: 100%;
41 41
42 -webkit-transition: -webkit-transform 0.2s ease-in-out; 42 -webkit-transition: -webkit-transform 0.2s ease-in-out;
43 -moz-transition: -moz-transform 0.2s ease-in-out; 43 -moz-transition: -moz-transform 0.2s ease-in-out;
src/app/components/proposal-ranking/proposal-ranking-carousel.scss 0 → 100644
@@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
  1 +.proposal-ranking {
  2 + // both: carousel and list
  3 +}
  4 +
  5 +.proposal-ranking--carousel {
  6 + background-color: #f1f1f1;
  7 +
  8 + &-top {
  9 + position: relative;
  10 + color: #fff;
  11 + font-weight: bold;
  12 + font-size: 18px;
  13 + padding: 5px 10px;
  14 +
  15 +
  16 + &-triggers {
  17 + position: absolute;
  18 + right: 5px;
  19 + top: 5px;
  20 +
  21 + li {
  22 + border: 1px solid #fff;
  23 + border-radius: 100%;
  24 + width: 15px;
  25 + height: 15px;
  26 + margin-right: 5px;
  27 +
  28 + cursor: pointer;
  29 +
  30 + &.active {
  31 + background-color: #fff;
  32 + }
  33 + }
  34 + }
  35 + }
  36 +
  37 + &-middle {
  38 + position: relative;
  39 + padding: 25px 30px;
  40 +
  41 + .content {
  42 + position: relative;
  43 + z-index: 2;
  44 + }
  45 +
  46 + &-watermark {
  47 + position: absolute;
  48 + bottom: 1px;
  49 + left: -5px;
  50 + color: #ddd;
  51 + font-size: 150px;
  52 + font-weight: bold;
  53 + line-height: 116px;
  54 + z-index: 1;
  55 +
  56 + }
  57 + }
  58 +
  59 + &-bottom {
  60 + position: relative;
  61 + color: #fff;
  62 + padding: 10px;
  63 + font-weight: bold;
  64 + cursor: pointer;
  65 + z-index: 10;
  66 +
  67 + &-icon {
  68 + .glyphicon {
  69 + position: relative;
  70 + top: -2px;
  71 + background-color: #fff;
  72 + padding: 6px 5px 5px 6px;
  73 + border-radius: 100%;
  74 + }
  75 + }
  76 + }
  77 +}
src/app/components/proposal-ranking/proposal-ranking-list.scss 0 → 100644
@@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
  1 +.proposal-ranking {
  2 + // both: carousel and list
  3 +}
  4 +
  5 +.proposal-ranking--list {
  6 + table {
  7 + border-radius: 4px;
  8 + overflow: hidden;
  9 + }
  10 +
  11 + thead {
  12 + th{
  13 + color: #fff;
  14 + background-color: #606060;
  15 + &:first-child {
  16 + background-color: #484848;
  17 + text-align: right;
  18 + width: 160px;
  19 + padding-right: 20px;
  20 + }
  21 + }
  22 +
  23 + .btn-question {
  24 + color: #484848;
  25 + background-color: #fff;
  26 + display: inline-block;
  27 + text-align: center;
  28 + width: 22px;
  29 + height: 22px;
  30 + margin-left: 10px;
  31 + padding: 0;
  32 + border-radius: 100%;
  33 + }
  34 +
  35 + .popover {
  36 + color: #484848;
  37 + }
  38 + }
  39 +
  40 + tbody {
  41 + tr {
  42 + background-color: #fff;
  43 + }
  44 +
  45 + td {
  46 + &:first-child {
  47 + font-size: 22px;
  48 + font-weight: bold;
  49 + padding: 0;
  50 + text-align: right;
  51 + }
  52 + }
  53 +
  54 + .position {
  55 + display: block;
  56 + width: 100%;
  57 + margin: 20px 0;
  58 + padding: 5px 20px;
  59 + border-top-right-radius: 4px;
  60 + border-bottom-right-radius: 4px;
  61 + }
  62 + }
  63 +
  64 + // override bootstrap
  65 + .table-striped > tbody > tr:nth-of-type(odd) {
  66 + // impar
  67 + background-color: #eaeaea;
  68 +
  69 + .position {
  70 + background-color: #fff;
  71 + }
  72 + }
  73 +
  74 + .table-striped > tbody > tr:nth-of-type(even) {
  75 + // par
  76 + background-color: #fff;
  77 +
  78 + .position {
  79 + background-color: #eaeaea;
  80 + }
  81 + }
  82 +}
src/app/components/proposal-ranking/proposal-ranking.directive.js 0 → 100644
@@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
  1 +(function() {
  2 + 'use strict';
  3 +
  4 + angular
  5 + .module('dialoga')
  6 + .directive('proposalRanking', proposalRanking);
  7 +
  8 + /** @ngInject */
  9 + function proposalRanking() {
  10 +
  11 + /** @ngInject */
  12 + function ProposalRankingController(ArticleService, $scope, $element, $timeout, $log) {
  13 + $log.debug('ProposalRankingController');
  14 +
  15 + var vm = this;
  16 + vm.ArticleService = ArticleService;
  17 + vm.$scope = $scope;
  18 + vm.$element = $element;
  19 + vm.$timeout = $timeout;
  20 + vm.$log = $log;
  21 +
  22 + vm.init();
  23 + }
  24 +
  25 + ProposalRankingController.prototype.init = function () {
  26 + // initial values
  27 + var vm = this;
  28 +
  29 + vm.activeIndex = 1;
  30 + vm.attachedPopover = false;
  31 + vm.loading = false;
  32 +
  33 + if(!angular.isNumber(vm.limit)){
  34 + vm.limit = parseInt(vm.limit);
  35 + }
  36 +
  37 + vm.loadData();
  38 + };
  39 +
  40 + ProposalRankingController.prototype.loadData = function () {
  41 + // async values
  42 + var vm = this;
  43 +
  44 + vm.loading = true;
  45 +
  46 + // simulate delay
  47 + vm.$timeout(function(){
  48 + vm.loading = false;
  49 + }, 2000);
  50 + };
  51 +
  52 + ProposalRankingController.prototype.switchProposal = function (index) {
  53 + var vm = this;
  54 +
  55 + if(index > 0 && index <= limit) {
  56 + vm.activeIndex = index;
  57 + }else{
  58 + vm.$log.warn('[switchProposal] "index" not handled:', index);
  59 + }
  60 + };
  61 +
  62 + ProposalRankingController.prototype.showProposals = function () {
  63 + var vm = this;
  64 +
  65 + // notify parents - handled by parents
  66 + vm.$scope.$emit('see-proposals');
  67 + };
  68 +
  69 + ProposalRankingController.prototype.showPopover = function ($event) {
  70 + var vm = this;
  71 +
  72 + $event.stopPropagation();
  73 +
  74 + var target = $event.target;
  75 + var elPopover = angular.element(target);
  76 +
  77 + if(!vm.attachedPopover){
  78 + elPopover.popover({
  79 + html: true,
  80 + placement: 'top',
  81 + animation: true,
  82 + title: 'Regra de posição das propostas',
  83 + content: '<p>É calculada pelo saldo de interações das propostas (curtidas - não curtidas) dividido pela diferença de exibições entre elas.</p><p>O objetivo dessa correção é compensar o saldo de interações e a diferença de exibições das propostas que não tiveram muitas oportunidades de visualização ou das propostas que tiveram mais oportunidades de visualização que a média.</p><p>Com essa correção, é possível comparar propostas que entraram em diferentes momentos, durante todo o período da consulta.</p>'
  84 + });
  85 + vm.attachedPopover = true;
  86 + }
  87 +
  88 +
  89 + elPopover.popover('toggle');
  90 + };
  91 +
  92 + var directive = {
  93 + restrict: 'E',
  94 + templateUrl: 'app/components/proposal-ranking/proposal-ranking.html',
  95 + scope: {
  96 + limit: '&',
  97 + display: '='
  98 + },
  99 + controller: ProposalRankingController,
  100 + controllerAs: 'vm',
  101 + bindToController: true
  102 + };
  103 +
  104 +
  105 + return directive;
  106 + }
  107 +
  108 +})();
src/app/components/proposal-ranking/proposal-ranking.html 0 → 100644
@@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
  1 +<div class="proposal-ranking">
  2 +
  3 + <div class="proposal-ranking--carousel" ng-if="vm.display==='carousel'">
  4 + <div class="proposal-ranking--carousel-top color-theme-bg">
  5 + <div class="proposal-ranking--carousel-position">
  6 + <span>1º</span>
  7 + <span>Lugar</span>
  8 + </div>
  9 + <div class="proposal-ranking--carousel-top-triggers">
  10 + <ul class="list-inline">
  11 + <li ng-class="{'active': vm.activeIndex === 1}" ng-click="vm.switchProposal(1)"></li>
  12 + <li ng-class="{'active': vm.activeIndex === 2}" ng-click="vm.switchProposal(2)"></li>
  13 + <li ng-class="{'active': vm.activeIndex === 3}" ng-click="vm.switchProposal(3)"></li>
  14 + </ul>
  15 + </div>
  16 + </div>
  17 + <div class="proposal-ranking--carousel-middle">
  18 + <div class="content">
  19 + <p>Lorem ipsum dolor sit amet, an qui alia constituam. Forensibus scripserit pri at, sit et dolorum ancillae. Ad sea quas utamur salutandi, illud veritus propriae mea ut. Ius no timeam intellegat liberavisse, eum suscipit pertinax ad. Illum graeci postulant et pro, at clita facete quo, cibo liber ad pri.</p>
  20 + </div>
  21 + <div class="proposal-ranking--carousel-middle-watermark"><span>1º</span></div>
  22 + </div>
  23 + <div class="proposal-ranking--carousel-bottom color-theme-common-bg" ng-click="vm.showProposals()">
  24 + <div class="proposal-ranking--carousel-bottom-icon">
  25 + <span>Veja as propostas mais vortadas</span>
  26 + <span class="glyphicon glyphicon-chevron-down pull-right color-theme-common-fg"></span>
  27 + </div>
  28 + </div>
  29 + </div>
  30 +
  31 + <div class="proposal-ranking--list" ng-if="vm.display==='list'">
  32 +
  33 + <div class="table-responsive" ng-if="vm.loading">
  34 + <div class="table-responsive">Carregando...</div>
  35 + </div>
  36 + <div class="table-responsive" ng-if="!vm.loading">
  37 + <table class="table table-striped">
  38 + <thead>
  39 + <tr>
  40 + <th>
  41 + Colocação
  42 + <button type="button" class="btn btn-link btn-question" ng-click="vm.showPopover($event)">?</button>
  43 + </th>
  44 + <th>123 PROPOSTAS</th>
  45 + </tr>
  46 + </thead>
  47 + <tbody>
  48 + <tr>
  49 + <td class="color-theme-fg">
  50 + <span class="position">1º</span>
  51 + </td>
  52 + <td>
  53 + <div class="row">
  54 + <div class="col-xs-12">Conteúdo</div>
  55 + <div class="col-md-6">
  56 + <button type="button" class="btn btn-link">
  57 + Avalie esta proposta
  58 + <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
  59 + <!-- <span class="glyphicon glyphicon-menu-right" aria-hidden="true"></span> -->
  60 + </button>
  61 + </div>
  62 + <div class="col-md-6">
  63 + <proposal-ranking views="'1'"></proposal-ranking>
  64 + </div>
  65 + </div>
  66 + </td>
  67 + </tr>
  68 + <tr>
  69 + <td class="color-theme-fg">
  70 + <span class="position">2º</span>
  71 + </td>
  72 + <td>
  73 + <div class="row">
  74 + <div class="col-xs-12">Conteúdo</div>
  75 + <div class="col-md-6">Avalie esta proposta</div>
  76 + <div class="col-md-6">[STATS]</div>
  77 + </div>
  78 + </td>
  79 + </tr>
  80 + </tbody>
  81 + </table>
  82 + </div>
  83 + </div>
  84 +</div>
src/app/components/proposal-related/proposal-related.directive.js 0 → 100644
src/app/components/proposal-related/proposal-related.html 0 → 100644
src/app/components/proposal-related/proposal-related.scss 0 → 100644
src/app/components/proposal-stats/proposal-stats.directive.js 0 → 100644
@@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
  1 +(function() {
  2 + 'use strict';
  3 +
  4 + angular
  5 + .module('dialoga')
  6 + .directive('proposalStats', proposalStats);
  7 +
  8 + /** @ngInject */
  9 + function proposalStats() {
  10 +
  11 + /** @ngInject */
  12 + function ProposalStatsController($log) {
  13 + $log.debug('ProposalStatsController');
  14 +
  15 + var vm = this;
  16 + vm.$log = $log;
  17 +
  18 + vm.init();
  19 + }
  20 +
  21 + ProposalStatsController.prototype.init = function () {
  22 + // initial values
  23 + var vm = this;
  24 +
  25 + vm.views = vm.views ? parseInt(vm.views) : 0;
  26 + vm.up = vm.up ? parseInt(vm.up) : 0;
  27 + vm.down = vm.down ? parseInt(vm.down) : 0;
  28 +
  29 + vm.loadData();
  30 + };
  31 +
  32 + ProposalStatsController.prototype.loadData = function () {
  33 + // async values
  34 + // var vm = this;
  35 + };
  36 +
  37 + var directive = {
  38 + restrict: 'E',
  39 + templateUrl: 'app/components/proposal-stats/proposal-stats.html',
  40 + scope: {
  41 + views: '&',
  42 + up: '&',
  43 + down: '&'
  44 + },
  45 + controller: ProposalStatsController,
  46 + controllerAs: 'vm',
  47 + bindToController: true
  48 + };
  49 +
  50 +
  51 + return directive;
  52 + }
  53 +
  54 +})();
src/app/components/proposal-stats/proposal-stats.html 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +<ul class="list-inline">
  2 + <li>
  3 + <span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
  4 + <span>{{vm.views}}</span>
  5 + </li>
  6 + <li>
  7 + <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
  8 + <span>{{vm.up}}</span>
  9 + </li>
  10 + <li>
  11 + <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
  12 + <span>{{vm.down}}</span>
  13 + </li>
  14 +</ul>
src/app/index.config.js
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 .module('dialoga') 5 .module('dialoga')
6 .config(configAuthInterceptor) 6 .config(configAuthInterceptor)
7 .config(configLocationProvider) 7 .config(configLocationProvider)
  8 + .config(configBreadcrumbProvider)
8 .config(config); 9 .config(config);
9 10
10 /** @ngInject */ 11 /** @ngInject */
@@ -38,6 +39,14 @@ @@ -38,6 +39,14 @@
38 } 39 }
39 40
40 /** @ngInject */ 41 /** @ngInject */
  42 + function configBreadcrumbProvider($breadcrumbProvider) {
  43 + $breadcrumbProvider.setOptions({
  44 + prefixStateName: 'inicio',
  45 + templateUrl: 'app/components/breadcrumb/template.html'
  46 + });
  47 + }
  48 +
  49 + /** @ngInject */
41 function config($logProvider) { 50 function config($logProvider) {
42 51
43 // Enable log 52 // Enable log
src/app/index.module.js
@@ -2,6 +2,6 @@ @@ -2,6 +2,6 @@
2 'use strict'; 2 'use strict';
3 3
4 angular 4 angular
5 - .module('dialoga', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ui.router', 'socialLinks', 'slugifier']); 5 + .module('dialoga', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ui.router', 'socialLinks', 'slugifier', 'ncy-angular-breadcrumb']);
6 6
7 })(); 7 })();
src/app/index.route.js
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 $stateProvider 10 $stateProvider
11 .state('inicio', { 11 .state('inicio', {
12 url: '/?limite&tema', 12 url: '/?limite&tema',
  13 + ncyBreadcrumb: {label: 'Home'},
13 reloadOnSearch: false, 14 reloadOnSearch: false,
14 views: { 15 views: {
15 'header': { templateUrl: 'app/pages/header/header.html' }, 16 'header': { templateUrl: 'app/pages/header/header.html' },
@@ -23,6 +24,7 @@ @@ -23,6 +24,7 @@
23 }) 24 })
24 .state('entrar', { 25 .state('entrar', {
25 url: '/entrar', 26 url: '/entrar',
  27 + ncyBreadcrumb: {label: 'Entrar'},
26 views: { 28 views: {
27 'header': { templateUrl: 'app/pages/header/header.html' }, 29 'header': { templateUrl: 'app/pages/header/header.html' },
28 'main': { 30 'main': {
@@ -35,6 +37,7 @@ @@ -35,6 +37,7 @@
35 }) 37 })
36 .state('cadastrar', { 38 .state('cadastrar', {
37 url: '/cadastrar', 39 url: '/cadastrar',
  40 + ncyBreadcrumb: {label: 'Cadastrar'},
38 views: { 41 views: {
39 'header': { templateUrl: 'app/pages/header/header.html' }, 42 'header': { templateUrl: 'app/pages/header/header.html' },
40 'main': { 43 'main': {
@@ -47,6 +50,7 @@ @@ -47,6 +50,7 @@
47 }) 50 })
48 .state('programas', { 51 .state('programas', {
49 url: '/programas', 52 url: '/programas',
  53 + ncyBreadcrumb: {label: 'Programas'},
50 views: { 54 views: {
51 'header': { templateUrl: 'app/pages/header/header.html' }, 55 'header': { templateUrl: 'app/pages/header/header.html' },
52 'main': { 56 'main': {
@@ -59,6 +63,10 @@ @@ -59,6 +63,10 @@
59 }) 63 })
60 .state('programa-conteudo', { 64 .state('programa-conteudo', {
61 url: '/programa/:slug', 65 url: '/programa/:slug',
  66 + ncyBreadcrumb: {
  67 + label: '{{$parent.$root.contentTitle}}',
  68 + parent: 'programas'
  69 + },
62 views: { 70 views: {
63 'header': { templateUrl: 'app/pages/header/header.html' }, 71 'header': { templateUrl: 'app/pages/header/header.html' },
64 'main': { 72 'main': {
@@ -71,6 +79,7 @@ @@ -71,6 +79,7 @@
71 }) 79 })
72 .state('propostas', { 80 .state('propostas', {
73 url: '/propostas', 81 url: '/propostas',
  82 + ncyBreadcrumb: {label: 'Propostas'},
74 views: { 83 views: {
75 'header': { templateUrl: 'app/pages/header/header.html' }, 84 'header': { templateUrl: 'app/pages/header/header.html' },
76 'main': { 85 'main': {
@@ -81,9 +90,25 @@ @@ -81,9 +90,25 @@
81 'footer': { templateUrl: 'app/pages/footer/footer.html' } 90 'footer': { templateUrl: 'app/pages/footer/footer.html' }
82 } 91 }
83 }) 92 })
84 - .state('propostas-details', {}) 93 + .state('propostas-conteudo', {
  94 + url: '/propostas/:id',
  95 + ncyBreadcrumb: {
  96 + label: '{{$parent.$root.contentTitle}}',
  97 + parent: 'propostas'
  98 + },
  99 + views: {
  100 + 'header': { templateUrl: 'app/pages/header/header.html' },
  101 + 'main': {
  102 + templateUrl: 'app/pages/propostas/proposta.html',
  103 + controller: 'PropostasPageController',
  104 + controllerAs: 'pagePropostas'
  105 + },
  106 + 'footer': { templateUrl: 'app/pages/footer/footer.html' }
  107 + }
  108 + })
85 .state('duvidas', { 109 .state('duvidas', {
86 url: '/duvidas', 110 url: '/duvidas',
  111 + ncyBreadcrumb: {label: 'Dúvidas'},
87 views: { 112 views: {
88 'header': { templateUrl: 'app/pages/header/header.html' }, 113 'header': { templateUrl: 'app/pages/header/header.html' },
89 'main': { 114 'main': {
@@ -96,6 +121,7 @@ @@ -96,6 +121,7 @@
96 }) 121 })
97 .state('sobre', { 122 .state('sobre', {
98 url: '/sobre', 123 url: '/sobre',
  124 + ncyBreadcrumb: {label: 'Sobre'},
99 views: { 125 views: {
100 'header': { templateUrl: 'app/pages/header/header.html' }, 126 'header': { templateUrl: 'app/pages/header/header.html' },
101 'main': { 127 'main': {
@@ -108,6 +134,7 @@ @@ -108,6 +134,7 @@
108 }) 134 })
109 .state('termos-de-uso', { 135 .state('termos-de-uso', {
110 url: '/termos-de-uso', 136 url: '/termos-de-uso',
  137 + ncyBreadcrumb: {label: 'Termos de Uso'},
111 controller: 'ArticlePageController', 138 controller: 'ArticlePageController',
112 views: { 139 views: {
113 'header': { templateUrl: 'app/pages/header/header.html' }, 140 'header': { templateUrl: 'app/pages/header/header.html' },
@@ -121,6 +148,7 @@ @@ -121,6 +148,7 @@
121 }) 148 })
122 .state('mapa-do-site', { 149 .state('mapa-do-site', {
123 url: '/mapa-do-site', 150 url: '/mapa-do-site',
  151 + ncyBreadcrumb: {label: 'Mapa do Site'},
124 views: { 152 views: {
125 'header': { templateUrl: 'app/pages/header/header.html' }, 153 'header': { templateUrl: 'app/pages/header/header.html' },
126 'main': { templateUrl: 'app/pages/sitemap/sitemap.html' }, 154 'main': { templateUrl: 'app/pages/sitemap/sitemap.html' },
src/app/index.run.js
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 .run(runHistory) 9 .run(runHistory)
10 .run(runPath) 10 .run(runPath)
11 .run(runColorUtils) 11 .run(runColorUtils)
  12 + .run(runUtils)
12 .run(runBlock); 13 .run(runBlock);
13 14
14 /** @ngInject */ 15 /** @ngInject */
@@ -51,23 +52,6 @@ @@ -51,23 +52,6 @@
51 angular.element(bodyEl).toggleClass('contraste', !!state); 52 angular.element(bodyEl).toggleClass('contraste', !!state);
52 } 53 }
53 54
54 - $rootScope.skipToContent = function() {  
55 - angular.element('#content').attr('tabIndex', -1).focus();  
56 - };  
57 -  
58 - $rootScope.skipToNavigation = function() {  
59 - angular.element('#navigation').attr('tabIndex', -1).focus();  
60 - };  
61 -  
62 - $rootScope.skipToSearch = function() {  
63 - // angular.element('#search').attr('tabIndex', -1).focus();  
64 - angular.element('#articleQueryFilter').attr('tabIndex', -1).focus();  
65 - };  
66 -  
67 - $rootScope.skipToFooter = function() {  
68 - angular.element('#footer').attr('tabIndex', -1).focus();  
69 - };  
70 -  
71 $rootScope.actionContrast = function() { 55 $rootScope.actionContrast = function() {
72 // toggle contrast 56 // toggle contrast
73 contrast = !contrast; 57 contrast = !contrast;
@@ -75,32 +59,52 @@ @@ -75,32 +59,52 @@
75 adjustContrast(contrast); 59 adjustContrast(contrast);
76 }; 60 };
77 61
78 - $rootScope.focusMainContent = function($event) { 62 + $rootScope.focusOn = function(elId, $event) {
  63 + var el = angular.element(elId);
  64 + $rootScope.scrollTo(el, $event);
  65 + el.attr('tabIndex', -1).focus();
  66 + };
79 67
80 - // prevent skip link from redirecting  
81 - if ($event) { $event.preventDefault(); } 68 + $rootScope.focusMainContent = function($event) {
82 69
83 var mainContentArea = document.querySelector('[role="main"]'); 70 var mainContentArea = document.querySelector('[role="main"]');
84 71
85 if (mainContentArea) { 72 if (mainContentArea) {
86 $timeout(function() { 73 $timeout(function() {
87 - var $el = angular.element(mainContentArea);  
88 -  
89 - angular.element('body').animate({scrollTop: $el.offset().top}, 'slow');  
90 - }, 90); 74 + $rootScope.scrollTo(mainContentArea, $event);
  75 + }, 90); // force queue
91 } else { 76 } else {
92 $log.warn('role="main" not found.'); 77 $log.warn('role="main" not found.');
93 } 78 }
94 }; 79 };
95 80
  81 + $rootScope.scrollTo = function(target, $event) {
  82 +
  83 + // prevent skip link from redirecting
  84 + if ($event) { $event.preventDefault(); }
  85 +
  86 + if(angular.isString(target)){
  87 + target = angular.element(target);
  88 + }
  89 +
  90 + angular.element('body').animate({scrollTop: target.offset().top}, 'fast');
  91 + };
  92 +
96 $log.debug('[RUN] Accessibility end.'); 93 $log.debug('[RUN] Accessibility end.');
97 } 94 }
98 95
99 /** @ngInject */ 96 /** @ngInject */
100 function runHistory($rootScope) { 97 function runHistory($rootScope) {
  98 + var MAX_HISTORY = 20;
  99 + $rootScope.$previousState = $rootScope.$previousState || [];
101 $rootScope.$on('$stateChangeSuccess', function(event, toState, toStateParams, fromState, fromStateParams) { 100 $rootScope.$on('$stateChangeSuccess', function(event, toState, toStateParams, fromState, fromStateParams) {
102 - $rootScope.$previousState = { state: fromState, params: fromStateParams}; 101 + $rootScope.$previousState.push({ state: fromState, params: fromStateParams});
  102 + $rootScope.$previousState.splice(-MAX_HISTORY, MAX_HISTORY);
103 }); 103 });
  104 +
  105 + $rootScope.goBack = $rootScope.goBack || function () {
  106 + return $rootScope.$previousState.pop();
  107 + };
104 } 108 }
105 109
106 /** @ngInject */ 110 /** @ngInject */
@@ -140,6 +144,13 @@ @@ -140,6 +144,13 @@
140 } 144 }
141 145
142 /** @ngInject */ 146 /** @ngInject */
  147 + function runUtils($rootScope) {
  148 + $rootScope.stripHtml = function (text) {
  149 + return String(text).replace(/<[^>]+>/gm, '');
  150 + };
  151 + }
  152 +
  153 + /** @ngInject */
143 function runBlock($log) { 154 function runBlock($log) {
144 $log.debug('[RUN] Block end.'); 155 $log.debug('[RUN] Block end.');
145 } 156 }
src/app/index.scss
@@ -49,7 +49,7 @@ body { @@ -49,7 +49,7 @@ body {
49 49
50 // Commons 50 // Commons
51 .button--themed { 51 .button--themed {
52 - padding: 20px; 52 + // padding: 20px;
53 .btn { 53 .btn {
54 color: #fff; 54 color: #fff;
55 font-weight: bold; 55 font-weight: bold;
@@ -132,6 +132,24 @@ body { @@ -132,6 +132,24 @@ body {
132 } 132 }
133 } 133 }
134 134
  135 +.icon-wrapper-rounded {
  136 + padding: 3px;
  137 + border-radius: 100%;
  138 +}
  139 +
  140 +// Theme
  141 +@each $category, $color in $categories {
  142 + .#{$category} {
  143 + .color-theme-fg { color: $color; }
  144 + .color-theme-bg { background-color: $color;}
  145 + }
  146 +}
  147 +
  148 +$common-color: #5E739E;
  149 +.color-theme-common-fg {color: $common-color; }
  150 +.color-theme-common-bg {background-color: $common-color; }
  151 +
  152 +
135 // Hack to fix "Barra do Brasil" 153 // Hack to fix "Barra do Brasil"
136 #barra-brasil .brasil-flag { 154 #barra-brasil .brasil-flag {
137 height: 33px !important; 155 height: 33px !important;
src/app/pages/footer/footer.html
@@ -3,7 +3,8 @@ @@ -3,7 +3,8 @@
3 <div class="row"> 3 <div class="row">
4 <div class="col-xs-12 text-center"> 4 <div class="col-xs-12 text-center">
5 <a id="termos-de-uso" ui-sref="termos-de-uso">Termos de uso</a> 5 <a id="termos-de-uso" ui-sref="termos-de-uso">Termos de uso</a>
6 - </div> 6 + <a href="#header" class="pull-right" ng-click="scrollTo('#header')">Voltar para o topo</a>
  7 + </div>
7 </div> 8 </div>
8 </div> 9 </div>
9 </div> 10 </div>
src/app/pages/header/header.html
1 -<header class="container"> 1 +<header id="header" class="container">
2 2
3 <div class="row"> 3 <div class="row">
4 <div class="col-sm-12"> 4 <div class="col-sm-12">
src/app/pages/programas/programa-content.controller.js
@@ -21,47 +21,63 @@ @@ -21,47 +21,63 @@
21 vm.init(); 21 vm.init();
22 } 22 }
23 23
24 - ProgramaContentPageController.prototype.init = function () { 24 + ProgramaContentPageController.prototype.init = function() {
25 var vm = this; 25 var vm = this;
26 26
27 var params = vm.$state.params; 27 var params = vm.$state.params;
28 - var slug = params.slug;  
29 28
30 vm.article = null; 29 vm.article = null;
31 - vm.categories = null;  
32 - vm.currentCategory = null; 30 + vm.category = null;
33 vm.loading = true; 31 vm.loading = true;
34 vm.error = false; 32 vm.error = false;
  33 + vm.slug = params.slug;
35 34
36 - vm.ArticleService.getCategories(function(categories){  
37 - vm.categories = categories;  
38 - }, function (error) {  
39 - vm.error = error;  
40 - vm.$log.error(error);  
41 - }); 35 + vm.loadData();
  36 + vm.attachListeners();
  37 + };
42 38
43 - vm.ArticleService.getArticleBySlug(slug, function(article){ 39 + ProgramaContentPageController.prototype.loadData = function() {
  40 + var vm = this;
  41 +
  42 + vm.ArticleService.getArticleBySlug(vm.slug, function(article) {
44 vm.article = article; 43 vm.article = article;
45 - vm.currentCategory = vm.article.categories[0]; 44 + vm.category = vm.article.categories[0];
  45 +
  46 + vm.$rootScope.contentTitle = vm.article.title;
  47 +
  48 + if (!vm.banner) {
  49 + vm.banner = {
  50 + src: vm.$rootScope.basePath + vm.article.image.url,
  51 + alt: 'Imagem de destaque do conteúdo'
  52 + };
  53 + }
46 54
47 vm.loadContent(); 55 vm.loadContent();
48 56
49 - }, function (error) { 57 + }, function(error) {
50 vm.$log.error(error); 58 vm.$log.error(error);
51 vm.$log.info('Rollback to home page.'); 59 vm.$log.info('Rollback to home page.');
52 vm.$state.go('inicio', {}, {location: true}); 60 vm.$state.go('inicio', {}, {location: true});
53 }); 61 });
54 }; 62 };
55 63
56 - ProgramaContentPageController.prototype.loadContent = function () { 64 + ProgramaContentPageController.prototype.attachListeners = function() {
  65 + var vm = this;
  66 +
  67 + vm.$scope.$on('see-proposals', function() {
  68 + vm.$log.warn('TODO: handle see proposals / ranking');
  69 + });
  70 + };
  71 +
  72 + ProgramaContentPageController.prototype.loadContent = function() {
57 var vm = this; 73 var vm = this;
58 74
59 vm.loading = true; 75 vm.loading = true;
60 - if(!vm.article.body){  
61 - vm.ArticleService.getContentById(vm.article.id, function (data) { 76 + if (!vm.article.body) {
  77 + vm.ArticleService.getContentById(vm.article.id, function(data) {
62 vm.article.body = data.article.body; 78 vm.article.body = data.article.body;
63 vm.loading = false; 79 vm.loading = false;
64 - }, function (error) { 80 + }, function(error) {
65 vm.loading = false; 81 vm.loading = false;
66 vm.error = error; 82 vm.error = error;
67 }); 83 });
@@ -69,13 +85,9 @@ @@ -69,13 +85,9 @@
69 vm.loading = false; 85 vm.loading = false;
70 }; 86 };
71 87
72 - ProgramaContentPageController.prototype.goToPreview = function () { 88 + ProgramaContentPageController.prototype.makeProposal = function() {
73 var vm = this; 89 var vm = this;
74 90
75 - vm.$state.go('programa', {  
76 - slug: vm.article.slug  
77 - }, {  
78 - location: true  
79 - }); 91 + vm.$log.warn('Not implemented yet: "makeProposal"');
80 }; 92 };
81 })(); 93 })();
src/app/pages/programas/programa.html
1 -<div class="container page--conheca-o-programa">  
2 - <div ng-if="pageProgramaContent.article && pageProgramaContent.categories">  
3 - <article-bar category="pageProgramaContent.article.categories[0]" categories="pageProgramaContent.categories"></article-bar>  
4 - </div> 1 +<div class="page--conheca-o-programa">
  2 + <section>
  3 + <div class="container">
  4 + <div ng-if="pageProgramaContent.article && pageProgramaContent.article.title">
  5 + <div ncy-breadcrumb></div>
  6 + </div>
  7 + </div>
  8 + </section>
5 9
6 - <div ng-if="!pageProgramaContent.article.body">  
7 - <div ng-if="!pageProgramaContent.error" class="alert alert-info" role="alert">Carregando detalhes sobre o progama...</div>  
8 - <div ng-if="pageProgramaContent.error" class="alert alert-warning" role="alert">{{pageProgramaContent}}</div>  
9 - </div> 10 + <section>
  11 + <div class="container">
  12 + <div ng-if="!pageProgramaContent.article.body">
  13 + <div ng-if="!pageProgramaContent.error" class="alert alert-info" role="alert">Carregando detalhes sobre o progama...</div>
  14 + <div ng-if="pageProgramaContent.error" class="alert alert-warning" role="alert">{{pageProgramaContent}}</div>
  15 + </div>
  16 + </div>
  17 + </section>
10 18
11 - <div ng-if="pageProgramaContent.article.body">  
12 - <article class="program-content">  
13 - <div>  
14 - <section ng-bind-html="pageProgramaContent.article.body"></section> 19 + <div ng-if="pageProgramaContent.article.body" ng-class="pageProgramaContent.category.slug">
  20 + <section>
  21 + <div class="container">
  22 + <article class="program-preview">
  23 + <div class="row">
  24 + <div class="col-md-12">
  25 + <h1 class="program-preview--title color-theme-fg">{{::pageProgramaContent.article.title}}</h1>
  26 + </div>
  27 + <div class="col-md-8">
  28 + <div class="program-preview--box">
  29 + <div class="program-preview--banner" ng-style="{'background-image':'url( {{::pageProgramaContent.banner.src}} )'}"></div>
  30 + <div class="program-preview--box--content-wrapper">
  31 + <div class="program-preview--icon icon-wrapper-rounded color-theme-bg" ng-class="pageProgramaContent.category.slug">
  32 + <span class="icon" ng-class="'icon-tema-' + pageProgramaContent.category.slug"></span>
  33 + </div>
  34 + <div class="program-preview--abstract color-theme-fg">
  35 + <h2>{{::stripHtml(pageProgramaContent.article.abstract)}}</h2>
  36 + </div>
  37 + <div class="program-preview--abstract-details">
  38 + <p>Lorem ipsum dolor sit amet, ea veniam mucius ocurreret vix, ius ex nisl vidisse partiendo. Blandit nominavi cum ei, paulo quaestio his ei, eum minim salutandi in. Civibus albucius in quo, et eam posse facilisis. Debet suavitate sea ut, his ei feugiat fastidii eleifend. Quo ex quando maiestatis voluptatum, mel te perpetua maiestatis, sit ceteros legendos deserunt ea. Enim dolores moderatius eu pro, ad quo ignota aliquid meliore.</p>
  39 + </div>
  40 + <div class="program-preview--share">
  41 + <ul class="list-inline">
  42 + <li>Compartilhe este programa:</li>
  43 + <li><social-share class="program-preview--share-directive"></social-share></li>
  44 + </ul>
  45 + </div>
  46 + <div class="program-preview--make-proposal">
  47 + <div class="row">
  48 + <div class="col-sm-6">
  49 + <div class="button--themed">
  50 + <button type="button" class="btn btn-block" ng-click="pageProgramaContent.makeProposal()">Faça uma proposta</button>
  51 + </div>
  52 + </div>
  53 + </div>
  54 + </div>
  55 + </div>
  56 + </div>
  57 + </div>
  58 + <div class="col-md-4">
  59 + <div class="row">
  60 + <div class="col-xs-12">
  61 + <h3 class="color-theme-fg">Propostas mais votadas</h3>
  62 + <proposal-ranking limit="'3'" display="'carousel'"></proposal-ranking>
  63 + </div>
  64 + <div class="col-xs-12">
  65 + <h3 class="color-theme-fg">Propostas nesse programa</h3>
  66 + <proposal-related article="pageProgramaContent.article"></proposal-related>
  67 + </div>
  68 + </div>
  69 + </div>
  70 + </div>
  71 + </article>
15 </div> 72 </div>
16 - </article>  
17 - <aside class="program--aside"ng-class="pageProgramaContent.article.categories[0].slug">  
18 - <div class="col-sm-6" >  
19 - <div class="button--themed">  
20 - <button class="btn btn-block" ng-click="pageProgramaContent.goToPreview()">  
21 - <span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>  
22 - Participe  
23 - </button> 73 + </section>
  74 +
  75 + <section class="proposal-ranking-section">
  76 + <div class="container">
  77 + <div class="proposal-ranking-section-header">
  78 + <h3 class="color-theme-fg">Resultados de propostas mais votadas</h3>
  79 + </div>
  80 + <div class="proposal-ranking-section-table">
  81 + <proposal-ranking limit="'5'" display="'list'"></proposal-ranking>
24 </div> 82 </div>
25 </div> 83 </div>
26 - <div class="col-sm-6">  
27 - <social-share></social-share> 84 + </section>
  85 +
  86 + <article class="program-content">
  87 + <div class="row">
  88 + <section ng-bind-html="pageProgramaContent.article.body"></section>
28 </div> 89 </div>
29 - </aside> 90 + </article>
30 </div> 91 </div>
31 </div> 92 </div>
src/app/pages/programas/programas.scss
@@ -7,3 +7,58 @@ @@ -7,3 +7,58 @@
7 } 7 }
8 } 8 }
9 9
  10 +.program-preview {
  11 +
  12 + .program-preview--box {
  13 + position: relative;
  14 + background: #f1f1f1;
  15 + }
  16 +
  17 + .program-preview--banner {
  18 + width: 100%;
  19 + height: 400px;
  20 +
  21 + background-position: center;
  22 + background-size: cover;
  23 + background-repeat: no-repeat;
  24 + }
  25 +
  26 + .program-preview--icon {
  27 + $icon-size: 98px;
  28 + $icon-scale: 0.7;
  29 + position: absolute;
  30 + top: (-1) * ($icon-size / 2);
  31 + left: 40px;
  32 +
  33 + width: $icon-size * $icon-scale + 20px;
  34 + height: $icon-size * $icon-scale + 20px;
  35 +
  36 + .icon {
  37 + display: block;
  38 + position: relative;
  39 + top: -8px;
  40 + left: -6px;
  41 + transform: scale($icon-scale);
  42 + }
  43 + }
  44 +
  45 + .program-preview--box--content-wrapper {
  46 + padding: 40px;
  47 + position: relative;
  48 + }
  49 +
  50 + h2,
  51 + h3{
  52 + font-weight: bold;
  53 + }
  54 +
  55 + h3 {
  56 + margin-top: 0;
  57 + }
  58 +
  59 +}
  60 +
  61 +.proposal-ranking-section {
  62 + background-color: #f1f1f1;
  63 + margin: 30px 0;
  64 +}
src/app/pages/propostas/proposta.html 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<div class="container page--propostas">
  2 + <h1>TODO: Home > Proposta</h1>
  3 +</div>