workarea-share 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.gitignore +14 -0
  7. data/CHANGELOG.md +108 -0
  8. data/CODE_OF_CONDUCT.md +3 -0
  9. data/CONTRIBUTING.md +3 -0
  10. data/Gemfile +9 -0
  11. data/LICENSE +52 -0
  12. data/README.md +59 -0
  13. data/Rakefile +52 -0
  14. data/app/assets/images/workarea/storefront/share/icons/email.svg +4 -0
  15. data/app/assets/stylesheets/workarea/storefront/share/components/_share_buttons.scss +14 -0
  16. data/app/controllers/workarea/storefront/shares_controller.rb +42 -0
  17. data/app/helpers/workarea/storefront/shares_helper.rb +39 -0
  18. data/app/mailers/workarea/storefront/share_mailer.rb +17 -0
  19. data/app/models/workarea/email/share.rb +39 -0
  20. data/app/seeds/shares_seeds.rb +22 -0
  21. data/app/view_models/workarea/storefront/share_view_model.rb +43 -0
  22. data/app/views/workarea/storefront/pages/_shares_robots.text +1 -0
  23. data/app/views/workarea/storefront/products/_share.html.haml +1 -0
  24. data/app/views/workarea/storefront/share_mailer/share.html.haml +21 -0
  25. data/app/views/workarea/storefront/shares/_share_buttons.html.haml +16 -0
  26. data/app/views/workarea/storefront/shares/new.html.haml +50 -0
  27. data/app/views/workarea/storefront/style_guides/components/_share_buttons.html.haml +14 -0
  28. data/bin/rails +17 -0
  29. data/config/initializers/appends.rb +14 -0
  30. data/config/initializers/configuration.rb +6 -0
  31. data/config/initializers/rack_attack.rb +10 -0
  32. data/config/initializers/recaptcha.rb +9 -0
  33. data/config/locales/en.yml +30 -0
  34. data/config/routes.rb +5 -0
  35. data/lib/tasks/share_tasks.rake +4 -0
  36. data/lib/workarea/mailer_previews/storefront/share_mailer_preview.rb +10 -0
  37. data/lib/workarea/share.rb +13 -0
  38. data/lib/workarea/share/engine.rb +15 -0
  39. data/lib/workarea/share/version.rb +5 -0
  40. data/test/dummy/Rakefile +6 -0
  41. data/test/dummy/app/assets/config/manifest.js +4 -0
  42. data/test/dummy/app/assets/images/.keep +0 -0
  43. data/test/dummy/app/assets/javascripts/application.js +13 -0
  44. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  45. data/test/dummy/app/controllers/application_controller.rb +3 -0
  46. data/test/dummy/app/controllers/concerns/.keep +0 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/jobs/application_job.rb +2 -0
  49. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  50. data/test/dummy/app/models/concerns/.keep +0 -0
  51. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  52. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  53. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  54. data/test/dummy/bin/bundle +3 -0
  55. data/test/dummy/bin/rails +4 -0
  56. data/test/dummy/bin/rake +4 -0
  57. data/test/dummy/bin/setup +34 -0
  58. data/test/dummy/bin/update +29 -0
  59. data/test/dummy/config.ru +5 -0
  60. data/test/dummy/config/application.rb +17 -0
  61. data/test/dummy/config/boot.rb +5 -0
  62. data/test/dummy/config/cable.yml +9 -0
  63. data/test/dummy/config/environment.rb +5 -0
  64. data/test/dummy/config/environments/development.rb +54 -0
  65. data/test/dummy/config/environments/production.rb +86 -0
  66. data/test/dummy/config/environments/test.rb +43 -0
  67. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  68. data/test/dummy/config/initializers/assets.rb +11 -0
  69. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  70. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  71. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  72. data/test/dummy/config/initializers/inflections.rb +16 -0
  73. data/test/dummy/config/initializers/mime_types.rb +4 -0
  74. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  75. data/test/dummy/config/initializers/session_store.rb +5 -0
  76. data/test/dummy/config/initializers/workarea.rb +5 -0
  77. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  78. data/test/dummy/config/locales/en.yml +23 -0
  79. data/test/dummy/config/puma.rb +47 -0
  80. data/test/dummy/config/routes.rb +5 -0
  81. data/test/dummy/config/secrets.yml +22 -0
  82. data/test/dummy/config/spring.rb +6 -0
  83. data/test/dummy/lib/assets/.keep +0 -0
  84. data/test/dummy/log/.keep +0 -0
  85. data/test/dummy/public/404.html +67 -0
  86. data/test/dummy/public/422.html +67 -0
  87. data/test/dummy/public/500.html +66 -0
  88. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  89. data/test/dummy/public/apple-touch-icon.png +0 -0
  90. data/test/dummy/public/favicon.ico +1 -0
  91. data/test/helpers/shares_helper_test.rb +30 -0
  92. data/test/integration/workarea/storefront/sharing_integration_test.rb +40 -0
  93. data/test/models/workarea/email/share_test.rb +28 -0
  94. data/test/system/workarea/storefront/sharing_system_test.rb +51 -0
  95. data/test/test_helper.rb +10 -0
  96. data/test/view_models/workarea/storefront/share_view_model_test.rb +65 -0
  97. data/workarea-share.gemspec +22 -0
  98. metadata +168 -0
@@ -0,0 +1,42 @@
1
+ module Workarea
2
+ module Storefront
3
+ class SharesController < Storefront::ApplicationController
4
+ before_action :find_share
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ if verify_recaptcha(model: @share, env: Rails.env) && @share.save
11
+ flash[:success] = t(
12
+ 'workarea.storefront.flash_messages.share_message_sent',
13
+ recipient: @share.friendly_to
14
+ )
15
+ ShareMailer.share(@share.id.to_s).deliver_later
16
+ redirect_to @share.url
17
+ else
18
+ render :new
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def find_share
25
+ model = Email::Share.new(share_params)
26
+ @share = ShareViewModel.wrap(model, view_model_options)
27
+ end
28
+
29
+ def share_params
30
+ params.permit(
31
+ :url,
32
+ :title,
33
+ :to_email,
34
+ :to_name,
35
+ :from_email,
36
+ :from_name,
37
+ :message
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ module Workarea
2
+ module Storefront
3
+ module SharesHelper
4
+ def share_product_url(product, options = {})
5
+ product_url(product, options.merge(host: Workarea.config.host))
6
+ end
7
+
8
+ def facebook_query_string(url)
9
+ hash_without_blanks_to_query(u: url)
10
+ end
11
+
12
+ def twitter_query_string(url, message = nil)
13
+ hash_without_blanks_to_query(url: url, text: message)
14
+ end
15
+
16
+ def share_query_string(url, message = nil, image = nil)
17
+ hash_without_blanks_to_query(
18
+ url: url,
19
+ description: message,
20
+ media: image
21
+ )
22
+ end
23
+
24
+ def share_analytics_data(type, url)
25
+ {
26
+ event: 'share',
27
+ domEvent: 'click',
28
+ payload: { type: type, url: url }
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def hash_without_blanks_to_query(hash)
35
+ hash.delete_if { |_k, v| v.blank? }.to_query
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ShareMailer < ApplicationMailer
4
+ def share(id)
5
+ @share = ShareViewModel.wrap(Email::Share.find(id))
6
+ @content = Content::Email.find_content('share')
7
+
8
+ mail(
9
+ to: @share.to,
10
+ from: Workarea.config.email_from,
11
+ reply_to: @share.from,
12
+ subject: t('workarea.storefront.email.share.subject', from: @share.friendly_from)
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ module Workarea
2
+ class Email::Share
3
+ include ApplicationDocument
4
+
5
+ field :to_name, type: String
6
+ field :to_email, type: String
7
+ field :from_name, type: String
8
+ field :from_email, type: String
9
+ field :message, type: String
10
+ field :title, type: String
11
+ field :url, type: String
12
+
13
+ validates :to_email, presence: true, email: true
14
+ validates :from_email, presence: true, email: true
15
+ validates :message, length: {
16
+ maximum: Workarea.config.max_email_share_message_length
17
+ }
18
+ validates :url, presence: true
19
+
20
+ after_initialize :sanitize_url
21
+ before_validation :sanitize_url
22
+
23
+ private
24
+
25
+ def uri
26
+ URI.parse(url)
27
+ end
28
+
29
+ def sanitize_url
30
+ return unless url.present?
31
+ self.url =
32
+ if uri.query.present?
33
+ [uri.path, uri.query].join('?')
34
+ else
35
+ uri.path
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ module Workarea
2
+ class SharesSeeds
3
+ def perform
4
+ puts 'Adding email shares...'
5
+
6
+ product = Catalog::Product.sample
7
+
8
+ Email::Share.create!(
9
+ to_name: Faker::Name.name,
10
+ to_email: Faker::Internet.email,
11
+ from_name: Faker::Name.name,
12
+ from_email: Faker::Internet.email,
13
+ message: Faker::Lorem.sentence,
14
+ title: product.name,
15
+ url: "/products/#{product.to_param}"
16
+ )
17
+
18
+ puts 'Adding content emails for product sharing...'
19
+ Content::Email.create!(type: 'share', content: "You've Got a Message!")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ module Workarea
2
+ class Storefront::ShareViewModel < ApplicationViewModel
3
+ def to
4
+ if to_name.present?
5
+ "#{to_name} <#{to_email}>"
6
+ else
7
+ to_email
8
+ end
9
+ end
10
+
11
+ def from
12
+ if from_name.present?
13
+ "#{from_name} <#{from_email}>"
14
+ else
15
+ from_email
16
+ end
17
+ end
18
+
19
+ def from_name
20
+ model.from_name || options[:user].try(:name)
21
+ end
22
+
23
+ def from_email
24
+ model.from_email || options[:user].try(:email)
25
+ end
26
+
27
+ def friendly_from
28
+ if from_name.blank?
29
+ from_email
30
+ else
31
+ from_name
32
+ end
33
+ end
34
+
35
+ def friendly_to
36
+ if to_name.present?
37
+ "#{to_name} (#{to_email})"
38
+ else
39
+ to_email
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1 @@
1
+ Disallow: /share
@@ -0,0 +1 @@
1
+ = render 'workarea/storefront/shares/share_buttons', share_url: share_product_url(product, sku: product.options[:sku]), image: product_image_url(product.primary_image, :detail), message: t('workarea.storefront.products.share_message', title: product.browser_title, site: Workarea.config.site_name)
@@ -0,0 +1,21 @@
1
+ = append_partials('storefront.share_mailer.top')
2
+
3
+ - content_for :preheader_text do
4
+ = @content
5
+
6
+ %tr
7
+ %td
8
+ %h1= @content
9
+ %p
10
+ %strong= @share.from
11
+ %p= t('workarea.storefront.email.share.from_info_html')
12
+
13
+ %p= link_to @share.title, path_to_url(@share.url)
14
+
15
+ - if @share.message.present?
16
+ %p
17
+ %strong #{@share.from} #{t('workarea.storefront.email.share.from_message_html')}
18
+ %p= @share.message
19
+
20
+
21
+ = append_partials('storefront.share_mailer.bottom')
@@ -0,0 +1,16 @@
1
+ - if share_url.present?
2
+ %ul.share-buttons
3
+ - if share_url.present?
4
+ %li.share-buttons__item
5
+ = link_to "https://www.facebook.com/sharer/sharer.php?#{facebook_query_string(share_url)}", class: 'share-buttons__button', title: t('workarea.storefront.shares.share_on_facebook'), target: '_blank', data: { popup_button: '', analytics: share_analytics_data('facebook', share_url).to_json } do
6
+ = inline_svg('workarea/storefront/icons/facebook.svg', class: 'svg-icon svg-icon--blue')
7
+ %li.share-buttons__item
8
+ = link_to "https://twitter.com/intent/tweet?#{twitter_query_string(share_url, message)}", class: 'share-buttons__button', title: t('workarea.storefront.shares.share_on_twitter'), target: '_blank', data: { popup_button: '', analytics: share_analytics_data('twitter', share_url).to_json } do
9
+ = inline_svg('workarea/storefront/icons/twitter.svg', class: 'svg-icon svg-icon--blue')
10
+ %li.share-buttons__item
11
+ = link_to "https://pinterest.com/pin/create/button/?#{share_query_string(share_url, message, local_assigns[:image])}", class: 'share-buttons__button', title: t('workarea.storefront.shares.share_on_pinterest'), target: '_blank', data: { popup_button: '', analytics: share_analytics_data('pinterest', share_url).to_json } do
12
+ = inline_svg('workarea/storefront/icons/pinterest.svg', class: 'svg-icon svg-icon--blue')
13
+ - if message.present?
14
+ %li.share-buttons__item
15
+ = link_to new_share_path(url: share_url, title: message), class: 'share-buttons__button', rel: 'nofollow', title: t('workarea.storefront.shares.share_by_email'), data: { dialog_button: '', analytics: share_analytics_data('email', share_url).to_json } do
16
+ = inline_svg('workarea/storefront/share/icons/email.svg', class: 'svg-icon svg-icon--blue')
@@ -0,0 +1,50 @@
1
+ - @title = t('workarea.storefront.shares.share_by_email')
2
+
3
+ .view
4
+ %h1= t('workarea.storefront.shares.share_by_email')
5
+ - if @share.errors.any?
6
+ - @share.errors.full_messages.each do |message|
7
+ = render_message 'error', message, data: { analytics: validation_error_analytics_data('share').to_json }
8
+
9
+ = form_tag shares_path, method: 'post', id: 'email_friend_form' do
10
+ = hidden_field_tag 'title', @share.title
11
+ = hidden_field_tag 'url', @share.url
12
+
13
+ %fieldset
14
+ %legend
15
+ %span.heading.heading--2= t('workarea.storefront.shares.from')
16
+ .property-group
17
+ .property
18
+ = label_tag 'from_name', nil, class: 'property__name' do
19
+ %span.property__text= t('workarea.storefront.shares.name')
20
+ %span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
21
+ .value= text_field_tag 'from_name', @share.from_name, class: 'text-box', placeholder: t('workarea.storefront.shares.from_name_placeholder')
22
+ .property
23
+ = label_tag 'from_email', nil, class: 'property__name' do
24
+ %span.property__text= t('workarea.storefront.shares.email')
25
+ .value= email_field_tag 'from_email', @share.from_email, class: 'text-box', required: true, autocapitalize: 'off', autocorrect: 'off', placeholder: t('workarea.storefront.shares.from_email_placeholder')
26
+
27
+ %fieldset
28
+ %legend
29
+ %span.heading.heading--2= t('workarea.storefront.shares.to')
30
+ .property-group
31
+ .property
32
+ = label_tag 'to_name', nil, class: 'property__name' do
33
+ %span.property__text= t('workarea.storefront.shares.name')
34
+ %span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
35
+ .value= text_field_tag 'to_name', @share.to_name, class: 'text-box', placeholder: t('workarea.storefront.shares.to_name_placeholder')
36
+ .property
37
+ = label_tag 'to_email', nil, class: 'property__name' do
38
+ %span.property__text= t('workarea.storefront.shares.email')
39
+ .value= email_field_tag 'to_email', @share.to_email, class: 'text-box', required: true, autocapitalize: 'off', autocorrect: 'off', placeholder: t('workarea.storefront.shares.to_email_placeholder')
40
+ .property
41
+ = label_tag 'message', nil, class: 'property__name' do
42
+ %span.property__text= t('workarea.storefront.shares.message')
43
+ %span.property__requirement.property__requirement--optional= t('workarea.storefront.forms.optional')
44
+ .value= text_area_tag 'message', @share.message, class: 'text-box text-box--multi-line text-box--wide', placeholder: t('workarea.storefront.shares.message_placeholder'), maxlength: Workarea.config.max_email_share_message_length
45
+
46
+ .property= recaptcha_tags
47
+
48
+ .grid.grid--auto
49
+ %p.grid__cell= button_tag t('workarea.storefront.forms.send'), value: 'email_friend', class: 'button'
50
+ %p.grid__cell= link_to t('workarea.storefront.forms.cancel'), @share.url, class: 'text-button', data: { dialog_close_button: '' }
@@ -0,0 +1,14 @@
1
+ #share-buttons.style-guide__subsection
2
+
3
+ %h3= link_to_style_guide('components', 'share_buttons')
4
+
5
+ %p Displays a list of sharing links as a row of icons that reflow to fit the container width.
6
+
7
+ %p Link actions progressively enhanced by the <code>WORKAREA.popupButtons</code> module.
8
+
9
+ .style-guide__example-block
10
+ %ul.share-buttons
11
+ %li.share-buttons__item= link_to 'Share on Facebook', 'https://www.facebook.com/sharer/sharer.php', class: 'share-buttons__button icon icon--facebook', title: 'Share on Facebook', target: '_blank', data: { popup_button: '' }
12
+ %li.share-buttons__item= link_to 'Tweet on Twitter', 'https://twitter.com/intent/tweet', class: 'share-buttons__button icon icon--twitter', title: 'Tweet on Twitter', target: '_blank', data: { popup_button: '' }
13
+ %li.share-buttons__item= link_to 'Pin It on Pinterest', 'https://pinterest.com/pin/create/button/', class: 'share-buttons__button icon icon--pinterest', title: 'Pin It on Pinterest', target: '_blank', data: { popup_button: '' }
14
+ %li.share-buttons__item= link_to 'Share by Email', new_share_url(url: root_url, title: 'This is the message.'), class: 'share-buttons__button icon icon--envelope', rel: 'nofollow', title: 'Share by Email', data: { dialog_button: '' }
data/bin/rails ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/share/engine', __FILE__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'action_controller/railtie'
13
+ require 'action_mailer/railtie'
14
+ require 'sprockets/railtie'
15
+ require 'rails/test_unit/railtie'
16
+
17
+ require 'rails/engine/commands'
@@ -0,0 +1,14 @@
1
+ Workarea.append_partials(
2
+ 'storefront.product_details',
3
+ 'workarea/storefront/products/share'
4
+ )
5
+
6
+ Workarea.append_partials(
7
+ 'storefront.robots_txt',
8
+ 'workarea/storefront/pages/shares_robots'
9
+ )
10
+
11
+ Workarea.append_stylesheets(
12
+ 'storefront.components',
13
+ 'workarea/storefront/share/components/share_buttons'
14
+ )
@@ -0,0 +1,6 @@
1
+ Workarea.config.seeds.push('Workarea::SharesSeeds')
2
+
3
+ # The maximum message length (in characters) that an email share
4
+ # can contain. Used in +Workarea::Email::Share+ and the 'Share by
5
+ # Email' form in +app/views/workarea/storefront/shares/new+.
6
+ Workarea.config.max_email_share_message_length = 2000
@@ -0,0 +1,10 @@
1
+ class Rack::Attack
2
+ # Throttle POST requests to /shares by IP Address
3
+ #
4
+ # Key: "rack::attack:#{Time.now.to_i/:period}:shares/ip:#{req.ip}"
5
+ throttle('shares/ip', limit: 6, period: 60.seconds) do |request|
6
+ if request.path == '/shares' && request.post?
7
+ request.ip
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ if Rails.application.secrets.recaptcha.present?
2
+ recaptcha = Rails.application.secrets.recaptcha.symbolize_keys
3
+
4
+ Recaptcha.configure do |config|
5
+ config.site_key = recaptcha[:site_key]
6
+ config.secret_key = recaptcha[:secret_key]
7
+ config.proxy = recaptcha[:proxy] if recaptcha[:proxy].present?
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ en:
2
+ workarea:
3
+ storefront:
4
+ email:
5
+ share:
6
+ subject: A Message from %{from}
7
+ from_info_html: thought you might be interested in this.
8
+ from_info_text: thought you might be interested in this.
9
+ from_message_html: says
10
+ from_message_text: says
11
+ flash_messages:
12
+ share_message_sent: Thanks for sharing! An email has been sent to %{recipient}.
13
+ products:
14
+ share_message: Check out %{title} from %{site}
15
+ shares:
16
+ share_on_facebook: Share on Facebook
17
+ share_on_twitter: Tweet on Twitter
18
+ share_on_pinterest: Pin it on Pinterest
19
+ share_by_email: Share by Email
20
+ from: From
21
+ to: To
22
+ name: Name
23
+ email: Email
24
+ from_name_placeholder: Your Name
25
+ from_email_placeholder: Your Email
26
+ to_name_placeholder: Friend's Name
27
+ to_email_placeholder: Friend's Email
28
+ message: Message
29
+ message_placeholder: Add a personal message
30
+ send_email: Send Email
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Workarea::Storefront::Engine.routes.draw do
2
+ scope '(:locale)', constraints: Workarea::I18n.routes_constraint do
3
+ resources :shares, only: [:new, :create]
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :share do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,10 @@
1
+ module Workarea
2
+ module Storefront
3
+ class ShareMailerPreview < ActionMailer::Preview
4
+ def share
5
+ share = Email::Share.first
6
+ ShareMailer.share(share.id)
7
+ end
8
+ end
9
+ end
10
+ end