minimum-viable-product 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/mvp/application.js.erb +38 -0
  6. data/app/assets/javascripts/mvp/init/controllers.js +23 -0
  7. data/app/assets/stylesheets/mvp/application.scss +23 -0
  8. data/app/assets/stylesheets/mvp/components/buttons.scss +20 -0
  9. data/app/assets/stylesheets/mvp/components/fonts.scss +15 -0
  10. data/app/assets/stylesheets/mvp/components/spacing.scss +13 -0
  11. data/app/assets/stylesheets/mvp/components/typography.scss +40 -0
  12. data/app/assets/stylesheets/mvp/init/_reset.scss +43 -0
  13. data/app/assets/stylesheets/mvp/init/layout.scss +42 -0
  14. data/app/assets/stylesheets/mvp/layout/basic.scss +6 -0
  15. data/app/assets/stylesheets/mvp/layout/carousel.scss +128 -0
  16. data/app/assets/stylesheets/mvp/layout/cover.scss +111 -0
  17. data/app/assets/stylesheets/mvp/pages/sample.scss +14 -0
  18. data/app/assets/stylesheets/mvp/skin/style.scss +8 -0
  19. data/app/controllers/mvp/analytics_controller.rb +14 -0
  20. data/app/controllers/mvp/application_controller.rb +11 -0
  21. data/app/controllers/mvp/concerns/analytics_concern.rb +77 -0
  22. data/app/controllers/mvp/concerns/seo_concern.rb +25 -0
  23. data/app/controllers/mvp/concerns/session_concern.rb +40 -0
  24. data/app/controllers/mvp/seo_controller.rb +13 -0
  25. data/app/controllers/mvp/styleguide_controller.rb +17 -0
  26. data/app/helpers/mvp/analytics_helper.rb +4 -0
  27. data/app/helpers/mvp/application_helper.rb +15 -0
  28. data/app/helpers/mvp/bootstrap_helper.rb +38 -0
  29. data/app/helpers/mvp/seo_helper.rb +4 -0
  30. data/app/helpers/mvp/styleguide_helper.rb +4 -0
  31. data/app/models/concerns/slugification.rb +16 -0
  32. data/app/views/layouts/mvp/_header.html.erb +48 -0
  33. data/app/views/layouts/mvp/_instrumentation.html.erb +75 -0
  34. data/app/views/layouts/mvp/_meta.html.erb +37 -0
  35. data/app/views/layouts/mvp/application.html.erb +24 -0
  36. data/app/views/layouts/mvp/basic.html.erb +10 -0
  37. data/app/views/layouts/mvp/carousel.html.erb +13 -0
  38. data/app/views/layouts/mvp/cover.html.erb +13 -0
  39. data/bin/mvp +23 -0
  40. data/config/initializers/canonical_host.rb +3 -0
  41. data/config/initializers/client_side_validations.rb +20 -0
  42. data/config/initializers/cloudinary.rb +6 -0
  43. data/config/initializers/geocoder.rb +9 -0
  44. data/config/initializers/iteration.rb +33 -0
  45. data/config/initializers/rollbar.rb +59 -0
  46. data/config/initializers/segment.rb +5 -0
  47. data/config/initializers/simple_form.rb +165 -0
  48. data/config/initializers/simple_form_bootstrap.rb +149 -0
  49. data/config/initializers/spoof_ip.rb +33 -0
  50. data/config/initializers/ssl.rb +3 -0
  51. data/config/routes.rb +16 -0
  52. data/lib/mvp.rb +6 -0
  53. data/lib/mvp/engine.rb +20 -0
  54. data/lib/mvp/ext/nil.rb +5 -0
  55. data/lib/mvp/ext/string.rb +7 -0
  56. data/lib/mvp/version.rb +3 -0
  57. data/lib/tasks/sitemap.rake +42 -0
  58. metadata +521 -0
@@ -0,0 +1,111 @@
1
+ body.body-cover {
2
+ text-align: center;
3
+
4
+ .site-wrapper {
5
+ display: table;
6
+ width: 100%;
7
+ height: 100%; /* For at least Firefox */
8
+ min-height: 100%;
9
+ }
10
+ .site-wrapper-inner {
11
+ display: table-cell;
12
+ vertical-align: top;
13
+ }
14
+ .cover-container {
15
+ margin-right: auto;
16
+ margin-left: auto;
17
+ }
18
+
19
+ /* Padding for spacing */
20
+ .inner {
21
+ padding: 30px;
22
+ }
23
+
24
+
25
+ /*
26
+ * Header
27
+ */
28
+ .masthead-brand {
29
+ margin-top: 10px;
30
+ margin-bottom: 10px;
31
+ }
32
+
33
+ .masthead-nav > li {
34
+ display: inline-block;
35
+ }
36
+ .masthead-nav > li + li {
37
+ margin-left: 20px;
38
+ }
39
+ .masthead-nav > li > a {
40
+ padding-right: 0;
41
+ padding-left: 0;
42
+ font-size: 16px;
43
+ font-weight: bold;
44
+ }
45
+ .masthead-nav > li > a:hover,
46
+ .masthead-nav > li > a:focus {
47
+ background-color: transparent;
48
+ }
49
+ .masthead-nav > .active > a,
50
+ .masthead-nav > .active > a:hover,
51
+ .masthead-nav > .active > a:focus {
52
+
53
+ }
54
+
55
+ @media (min-width: 768px) {
56
+ .masthead-brand {
57
+ float: left;
58
+ }
59
+ .masthead-nav {
60
+ float: right;
61
+ }
62
+ }
63
+
64
+
65
+ /*
66
+ * Cover
67
+ */
68
+
69
+ .cover {
70
+ padding: 0 20px;
71
+ }
72
+ .cover .btn-lg {
73
+ padding: 10px 20px;
74
+ font-weight: bold;
75
+ }
76
+
77
+
78
+ /*
79
+ * Affix and center
80
+ */
81
+
82
+ @media (min-width: 768px) {
83
+ /* Pull out the header and footer */
84
+ .masthead {
85
+ position: fixed;
86
+ top: 0;
87
+ }
88
+ .mastfoot {
89
+ position: fixed;
90
+ bottom: 0;
91
+ }
92
+ /* Start the vertical centering */
93
+ .site-wrapper-inner {
94
+ vertical-align: middle;
95
+ }
96
+ /* Handle the widths */
97
+ .masthead,
98
+ .mastfoot,
99
+ .cover-container {
100
+ width: 100%; /* Must be percentage or pixels for horizontal alignment */
101
+ }
102
+ }
103
+
104
+ @media (min-width: 992px) {
105
+ .masthead,
106
+ .mastfoot,
107
+ .cover-container {
108
+ width: 700px;
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,14 @@
1
+ .home-pages {
2
+ .topic {
3
+ position:relative;
4
+
5
+ &:hover {
6
+ cursor: pointer;
7
+ }
8
+
9
+ p {
10
+ text-transform: uppercase;
11
+ font-weight: bold;
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ body {
2
+ background: #F3F3F3;
3
+ font-family: 'Open Sans', sans-serif;
4
+ }
5
+
6
+ h1,h2,h3,h4,h5,h6 {
7
+ font-weight: 700;
8
+ }
@@ -0,0 +1,14 @@
1
+ require_dependency "mvp/application_controller"
2
+
3
+ module Mvp
4
+ class AnalyticsController < ApplicationController
5
+ def optout
6
+ cookies[AnalyticsConcern::INVISIBLE_SESSION_COOKIE] = {
7
+ :value => true,
8
+ :expires => 1.year.from_now,
9
+ }
10
+
11
+ redirect_to root_path
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'concerns/analytics_concern'
2
+ require_relative 'concerns/seo_concern'
3
+ require_relative 'concerns/session_concern'
4
+
5
+ module Mvp
6
+ class ApplicationController < ActionController::Base
7
+ include Mvp::AnalyticsConcern
8
+ include Mvp::SEOConcern
9
+ include Mvp::SessionConcern
10
+ end
11
+ end
@@ -0,0 +1,77 @@
1
+ module Mvp
2
+ module AnalyticsConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ INVISIBLE_SESSION_COOKIE = 'invisible'
6
+
7
+ included do
8
+ before_action :generate_uid_if_not_set
9
+ helper_method :analytics_id
10
+ end
11
+
12
+ def track!(event, properties={})
13
+ return if session[INVISIBLE_SESSION_COOKIE].to_b == true
14
+ return unless analytics
15
+ analytics.track({
16
+ user_id: analytics_id,
17
+ event: event,
18
+ properties: properties.reverse_merge({
19
+ iteration: Mvp::Iteration.version,
20
+ user: current_user.try(:name),
21
+ url: request.fullpath
22
+ })
23
+ })
24
+ end
25
+
26
+ def slack!(message)
27
+ if ENV['SLACK_WEBHOOK_URL']
28
+ begin
29
+ slack_notifier.ping message
30
+ rescue
31
+ slack_notifier.ping "Tried to use slack messaging but failure happened"
32
+ end
33
+ end
34
+ end
35
+
36
+ def page!(name, opts={})
37
+ return if sessions[INVISIBLE_SESSION_COOKIE].to_b == true
38
+ return unless analytics
39
+ analytics.page(
40
+ user_id: current_user.try(:id),
41
+ name: name,
42
+ category: opts[:category],
43
+ properties: {
44
+ url: opts[:url],
45
+ iteration: Mvp::Iteration.version
46
+ }
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ def analytics
53
+ unless defined?(Analytics)
54
+ puts ""
55
+ puts "Please set SEGMENT_WRITE_KEY in .env"
56
+ puts ""
57
+ nil
58
+ else
59
+ Analytics
60
+ end
61
+ end
62
+
63
+ def slack_notifier
64
+ Slack::Notifier.new ENV['SLACK_WEBHOOK_URL'], channel: ENV['SLACK_WEBHOOK_URL'],
65
+ username: ENV['SLACK_POST_USERNAME'],
66
+ channel: ENV['SLACK_POST_CHANNEL']
67
+ end
68
+
69
+ def analytics_id
70
+ current_user.try(:id) || cookies[:uid]
71
+ end
72
+
73
+ def generate_uid_if_not_set
74
+ cookies[:uid] = SecureRandom.uuid unless cookies[:uid].present?
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ module Mvp
2
+ module SEOConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ included do
8
+ before_action :force_no_trailing_slash
9
+ helper_method :page
10
+ end
11
+
12
+ def force_no_trailing_slash
13
+ return if request.path == '/'
14
+ redirect_to request.original_url.gsub(/\/$/,'') if request.original_url.match(/\/$/)
15
+ end
16
+
17
+ def page
18
+ @page ||= begin
19
+ h = Hashie::Mash.new
20
+ h.classes = []
21
+ h
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module Mvp
2
+ module SessionConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ COOKIE_ID = '_session'
6
+
7
+ included do
8
+ helper_method :current_user
9
+ end
10
+
11
+ def sign_in(user)
12
+ session[COOKIE_ID] = user.id
13
+ end
14
+
15
+ def current_user
16
+ @current_user ||= begin
17
+ if session[COOKIE_ID]
18
+ Conversion.find(session[COOKIE_ID])
19
+ end
20
+ rescue ActiveRecord::RecordNotFound
21
+ session[COOKIE_ID] = nil
22
+ end
23
+ end
24
+
25
+ # http://blog.laaz.org/tech/2012/12/27/rails-redirect_back_or_default/
26
+ def store_location
27
+ session[:return_to] = if request.get?
28
+ request.url
29
+ else
30
+ request.referer
31
+ end
32
+ end
33
+
34
+ # http://blog.laaz.org/tech/2012/12/27/rails-redirect_back_or_default/
35
+ def redirect_back_or_default(default = root_url)
36
+ redirect_to(session.delete(:return_to) || request.referer || default)
37
+ end
38
+ alias_method :redirect_to_back_or_default, :redirect_back_or_default
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ require_dependency "mvp/application_controller"
2
+
3
+ module Mvp
4
+ class SeoController < ApplicationController
5
+ def robots
6
+ render text: %%
7
+ User-agent: *
8
+ Disallow:
9
+ Sitemap: #{sitemap_url}
10
+ %, content_type: "text/plain"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require_dependency "mvp/application_controller"
2
+
3
+ module Mvp
4
+ class StyleguideController < ApplicationController
5
+ def layout_basic
6
+ render template: 'styleguide/layouts/basic', layout: 'basic'
7
+ end
8
+
9
+ def layout_cover
10
+ render template: 'styleguide/layouts/cover', layout: 'cover'
11
+ end
12
+
13
+ def layout_carousel
14
+ render template: 'styleguide/layouts/carousel', layout: 'carousel'
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module Mvp
2
+ module AnalyticsHelper
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module Mvp
2
+ module ApplicationHelper
3
+ def body_id
4
+ "#{controller_name}-#{action_name}-page"
5
+ end
6
+
7
+ def body_classes
8
+ klass = []
9
+ klass << "#{controller_name}-pages"
10
+ klass << page.classes if page.classes.present?
11
+ klass << "hidden-header" if page.header == false
12
+ klass.join(' ')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Mvp
2
+ module BootstrapHelper
3
+ def col_groups_of(count, collection, opts={})
4
+ return if collection.blank?
5
+
6
+ html = []
7
+ collection.in_groups_of(count).each do |groups|
8
+ groups.compact.each do |obj|
9
+ html << capture do
10
+ content_tag :div, class: ["col-md-#{12/count.to_f.floor}", opts[:col_class]].compact.join(' ') do
11
+ yield obj
12
+ end
13
+ end.html_safe
14
+ end
15
+ end
16
+ concat html.join.html_safe
17
+ end
18
+
19
+ def row_groups_of(count, collection, opts={}, &block)
20
+ return if collection.blank?
21
+
22
+ html = []
23
+ subsetsize = (collection.count/count.to_f)
24
+ subsetsize.ceil.times do |i|
25
+ subset = collection[count*i...((count*i)+count)]
26
+ html << capture do
27
+ content_tag :div, class: ["row",opts[:row_class]].compact.join(' ') do
28
+ capture do
29
+ col_groups_of(count, subset, opts, &block)
30
+ end
31
+ end
32
+ end.html_safe
33
+ end
34
+
35
+ concat html.join.html_safe
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module Mvp
2
+ module SeoHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mvp
2
+ module StyleguideHelper
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module Slugification
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def slugify(name, opts={})
6
+ _opts = opts.reverse_merge(:if => lambda{ |obj| obj.send("#{name}_changed?".to_sym) })
7
+ before_validation -> { self.slug = slugify(self.send(name.to_sym)) }, _opts
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def slugify(str)
14
+ str.gsub(/\s/,'_').dasherize.downcase
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ <nav class="navbar navbar-default mt20">
2
+ <div class="container-fluid">
3
+ <!-- Brand and toggle get grouped for better mobile display -->
4
+ <div class="navbar-header">
5
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
6
+ <span class="sr-only">Toggle navigation</span>
7
+ <span class="icon-bar"></span>
8
+ <span class="icon-bar"></span>
9
+ <span class="icon-bar"></span>
10
+ </button>
11
+ <a class="navbar-brand" href="#">__PROJECT_NAME__</a>
12
+ </div>
13
+
14
+ <!-- Collect the nav links, forms, and other content for toggling -->
15
+ <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
16
+ <ul class="nav navbar-nav">
17
+ <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
18
+ <li><a href="#">Link</a></li>
19
+ <li class="dropdown">
20
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
21
+ <ul class="dropdown-menu">
22
+ <li><a href="#">Action</a></li>
23
+ <li><a href="#">Another action</a></li>
24
+ <li><a href="#">Something else here</a></li>
25
+ <li role="separator" class="divider"></li>
26
+ <li><a href="#">Separated link</a></li>
27
+ <li role="separator" class="divider"></li>
28
+ <li><a href="#">One more separated link</a></li>
29
+ </ul>
30
+ </li>
31
+ </ul>
32
+
33
+ <ul class="nav navbar-nav navbar-right">
34
+ <li><a href="#">Link</a></li>
35
+ <li class="dropdown">
36
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
37
+ <ul class="dropdown-menu">
38
+ <li><a href="#">Action</a></li>
39
+ <li><a href="#">Another action</a></li>
40
+ <li><a href="#">Something else here</a></li>
41
+ <li role="separator" class="divider"></li>
42
+ <li><a href="#">Separated link</a></li>
43
+ </ul>
44
+ </li>
45
+ </ul>
46
+ </div><!-- /.navbar-collapse -->
47
+ </div><!-- /.container-fluid -->
48
+ </nav>