workarea-share 1.2.4

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 (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