Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Registration Overhaul

  • Loading branch information...
commit b293d7d21693a1ac160d7c0a5cd10c921acdfdb2 1 parent e842a76
@jordanbyron jordanbyron authored
Showing with 492 additions and 345 deletions.
  1. +1 −0  Gemfile
  2. +3 −0  Gemfile.lock
  3. +3 −3 Procfile
  4. +8 −2 app/assets/javascripts/application.js
  5. +6 −0 app/assets/javascripts/github_redirect_warning.js.coffee
  6. +8 −8 app/assets/javascripts/pr.payment_processor.coffee
  7. +2 −0  app/assets/stylesheets/application.css.sass
  8. +13 −0 app/assets/stylesheets/partials/_email_confirmation_warning.sass
  9. +4 −6 app/assets/stylesheets/partials/_flash.sass
  10. +40 −0 app/assets/stylesheets/partials/_form.sass
  11. +8 −0 app/assets/stylesheets/partials/_landing.sass
  12. +5 −5 app/assets/stylesheets/partials/_library.sass
  13. +19 −36 app/assets/stylesheets/partials/_payments.sass
  14. +43 −0 app/assets/stylesheets/partials/_registration.sass
  15. +24 −16 app/assets/stylesheets/partials/_settings.sass
  16. +3 −3 app/controllers/application_controller.rb
  17. +6 −9 app/controllers/home_controller.rb
  18. +8 −48 app/controllers/registration_controller.rb
  19. +2 −2 app/controllers/sessions_controller.rb
  20. +25 −0 app/controllers/user_email_controller.rb
  21. +27 −6 app/controllers/users_controller.rb
  22. +4 −4 app/decorators/user_decorator.rb
  23. +7 −0 app/helpers/application_helper.rb
  24. +2 −1  app/models/payment_gateway/stripe.rb
  25. +22 −13 app/models/user.rb
  26. +4 −45 app/views/home/subscribe.html.haml
  27. +2 −3 app/views/layouts/application.html.haml
  28. +1 −0  app/views/layouts/landing.html.haml
  29. +0 −6 app/views/registration/confirm_email.html.haml
  30. +0 −12 app/views/registration/edit_profile.html.haml
  31. +23 −16 app/views/registration/payment.html.haml
  32. +0 −8 app/views/registration/payment_pending.html.haml
  33. +0 −11 app/views/registration/update_profile.html.haml
  34. +2 −2 app/views/registration_mailer/email_confirmation.text.erb
  35. +8 −6 app/views/shared/_broadcasts.html.haml
  36. +7 −6 app/views/shared/_credit_card.html.haml
  37. +1 −1  app/views/shared/_flash.html.haml
  38. +6 −0 app/views/user_email/_warning.html.haml
  39. +11 −0 app/views/user_email/change.html.haml
  40. +1 −0  app/views/user_email/dismiss_warning.js.coffee
  41. +3 −1 app/views/users/_settings_page.html.haml
  42. +14 −9 app/views/users/notifications.html.haml
  43. +7 −8 app/views/users/profile.html.haml
  44. +4 −0 app/views/users/update.js.coffee
  45. +1 −0  config/application.rb
  46. +12 −0 config/routes.rb
  47. +8 −0 db/migrate/20131204201817_add_email_confirmed_to_users.rb
  48. +2 −1  db/schema.rb
  49. +5 −0 test/factories/user_factory.rb
  50. +24 −16 test/integration/registration_test.rb
  51. +53 −32 test/support/simulated_user.rb
View
1  Gemfile
@@ -57,6 +57,7 @@ group :assets do
gem 'compass-rails'
gem 'sassy-buttons'
gem 'turbo-sprockets-rails3'
+ gem 'parsley-rails'
end
group :test do
View
3  Gemfile.lock
@@ -187,6 +187,8 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
+ parsley-rails (1.2.2.0)
+ railties (>= 3.0.0)
pg (0.16.0)
poltergeist (1.1.2)
capybara (~> 2.0.1)
@@ -343,6 +345,7 @@ DEPENDENCIES
nokogiri (~> 1.5.11)
omniauth-github (~> 1.0.1)
omniauth-oauth2 (~> 1.1.1)
+ parsley-rails
pg (= 0.16.0)
poltergeist
rack-pjax (~> 0.6.0)
View
6 Procfile
@@ -1,3 +1,3 @@
-web: rails s
-worker: rake jobs:work
-mail: mailcatcher -f
+web: script/rails s
+worker: bundle exec rake jobs:work
+mail: bundle exec mailcatcher -f
View
10 app/assets/javascripts/application.js
@@ -2,6 +2,7 @@
//= require jquery_ujs
//= require md_preview
//= require jquery.elastic
+//= require parsley
//= require_tree '../../../vendor/assets/javascripts'
//= require_self
//= require_tree .
@@ -10,14 +11,19 @@
var PR = PR ? PR : new Object();
PR.setupNamespace = function(namespace){
- if(PR[namespace] == undefined)
- PR[namespace] = {}
+ if(PR[namespace] == undefined)
+ PR[namespace] = {}
}
// Facebox Assets
$.facebox.settings.closeImage = '/assets/facebox/closelabel.png';
$.facebox.settings.loadingImage = '/assets/facebox/loading.gif';
+$(document).on('click', 'a[rel=facebox]', function(e) {
+ e.preventDefault();
+ $.facebox({ajax: $(this).attr('href')});
+});
+
PR.immediate = function(){
$('a[rel=tooltip]').tooltip();
$('.bigtext').each(function() {
View
6 app/assets/javascripts/github_redirect_warning.js.coffee
@@ -0,0 +1,6 @@
+$(document).on 'click', "a[href='/subscribe']", (e) ->
+ e.preventDefault()
+ $.facebox {ajax: '/subscribe'}, 'redirect-warning'
+ setTimeout ->
+ window.location.href = '/login'
+ , 5000
View
16 app/assets/javascripts/pr.payment_processor.coffee
@@ -3,8 +3,6 @@ class PR.PaymentProcessor
Stripe.setPublishableKey @key
$(document).on 'submit', "#payment-form", this.formSubmit
- $('.card-number').focus()
-
$(document).on 'click', 'a#show-cvc-help', (e) ->
$.facebox { div: '#cvc-help' }, 'cvc-help'
e.preventDefault()
@@ -18,13 +16,13 @@ class PR.PaymentProcessor
e.preventDefault()
formSubmit: (event) =>
- event.preventDefault();
+ event.preventDefault()
@form = $(event.currentTarget)
# disable the submit button to prevent repeated clicks
$('.submit-button').attr "disabled", "disabled"
- $(".payment-errors").text ""
+ $(".payment-errors").slideUp()
this.createSpinner()
@@ -64,10 +62,11 @@ class PR.PaymentProcessor
@form.get(0).submit()
logError: (message) =>
@spinner.stop()
- $(".payment-errors").text message
+ @spinnerTarget.removeClass("spinning")
+ $(".payment-errors").text(message).slideDown()
$(".submit-button").removeAttr "disabled"
createSpinner: =>
- spinnerTarget = @form.find('#processing-spinner')[0]
+ @spinnerTarget ||= @form.find('#processing-spinner')
spinnerOpts = {
lines: 9,
@@ -77,6 +76,7 @@ class PR.PaymentProcessor
corners: 0.8,
hwaccel: true,
speed: 1.6
- };
+ }
- @spinner = new Spinner(spinnerOpts).spin(spinnerTarget)
+ @spinner = new Spinner(spinnerOpts).spin(@spinnerTarget[0])
+ @spinnerTarget.addClass("spinning")
View
2  app/assets/stylesheets/application.css.sass
@@ -27,3 +27,5 @@
@import partials/admin
@import partials/payments
@import partials/archives
+@import partials/registration
+@import partials/email_confirmation_warning
View
13 app/assets/stylesheets/partials/_email_confirmation_warning.sass
@@ -0,0 +1,13 @@
+#email-confirmation-warning
+ font-family: Helvetica, sans-serif
+ @extend #share-alert
+ box-sizing: border-box
+ a.dismiss
+ float: right
+ color: #fff
+ font-size: 0.75em
+ line-height: 3em
+ strong
+ display: block
+ font-weight: bold
+ margin-bottom: 0.5em
View
10 app/assets/stylesheets/partials/_flash.sass
@@ -2,16 +2,14 @@
width: $page-width
margin: 0 auto
text-align: center
-
+ font-family: 'Helvetica', sans-serif
.flash
text-align: center
padding: 0.5em 1em
margin: 0.5em
display: inline-block
- +border-radius(5px)
- +single-box-shadow(#333, 0, 0, 5px, false, true)
-
- background-color: #f9f9f9
+ +border-radius(2px)
+ background-color: #FAFAE2
&.notice
background-color: #8c8
@@ -19,4 +17,4 @@
&.error
background-color: #c88
- color: #522
+ color: #522
View
40 app/assets/stylesheets/partials/_form.sass
@@ -2,6 +2,38 @@ form
input[type=submit]
+classy-button
+ label, input[type=text], input[type=email], select
+ font-size: 14px
+ font-weight: normal
+ line-height: 20px
+ label
+ display: block
+ margin-bottom: 5px
+ color: #333
+ font-size: 0.75em
+ input[type=text], input[type=email], select
+ display: inline-block
+ height: 20px
+ padding: 4px 6px
+ margin: 0
+ font-size: 14px
+ line-height: 20px
+ color: #555
+ +border-radius(3px)
+ input[type=text], input[type=email]
+ background-color: #fff
+ border: 1px solid #CCC
+ +single-box-shadow(rgba(0,0,0,0.075), 0, 1px, 1px, false, true)
+ &:focus
+ outline: 0
+ outline: thin dotted 9
+ border-color: $dark-blue
+ select
+ height: 30px
+ line-height: 30px
+ background-color: #fff
+ border: 1px solid #CCC
+
#errorExplanation
margin: 1em 0
padding: 1em
@@ -18,6 +50,14 @@ form
li
color: #522
+ul.parsley-error-list li
+ color: #9E4040
+ font-size: 0.75em
+ margin: 5px 0
+
+input.parsley-error
+ border: 1px solid #9E4040 !important
+
div.field_with_errors
display: inline-block
color: #522
View
8 app/assets/stylesheets/partials/_landing.sass
@@ -65,3 +65,11 @@ body.landing #top-bar
+money-button
font-size: 1.5em
text-decoration: none
+
+#facebox .redirect-warning
+ font-family: sans-serif
+ h1
+ font-weight: bold
+ p
+ font-size: 12px
+ color: #888
View
10 app/assets/stylesheets/partials/_library.sass
@@ -1,10 +1,10 @@
-$blueprint-grid-margin: 20px
-$blueprint-grid-width: 18px
-$blueprint-grid-columns: 20
+body.home-library
+ $blueprint-grid-margin: 20px
+ $blueprint-grid-width: 18px
+ $blueprint-grid-columns: 20
-@import "blueprint"
+ @import "blueprint/grid"
-body.home-library
p, #explore-articles
font-family: sans-serif
letter-spacing: normal
View
55 app/assets/stylesheets/partials/_payments.sass
@@ -9,46 +9,29 @@ p.note
a
color: #333
-div.payment-errors
- color: #9E4040
- font-size: 1.5em
- margin: 0.75em 0
+#payment-form
+ border-top: 1px solid #ddd
+ padding-top: 1em
-form#payment-form
- margin: 1em 0
- label, input[type=text], select
- font-size: 14px
- font-weight: normal
- line-height: 20px
- label
- display: block
- margin-bottom: 5px
- input[type=text], select
+ .payment-errors
+ display: none
+ margin-bottom: 1em
+ color: $red
+ font-weight: bold
+
+ .cc-num, .cc-cvc, .cc-exp
display: inline-block
- height: 20px
- padding: 4px 6px
- margin-bottom: 9px
- font-size: 14px
- line-height: 20px
- color: #555
- +border-radius(3px)
- input[type=text]
- background-color: #fff
- border: 1px solid #CCC
- +single-box-shadow(rgba(0,0,0,0.075), 0, 1px, 1px, false, true)
- &:focus
- outline: 0
- outline: thin dotted 9
- border-color: $dark-blue
- select
- height: 30px
- line-height: 30px
- background-color: #fff
- border: 1px solid #CCC
+ margin-right: 1.75em
+ .cc-exp
+ margin-right: 0
+
+ .card-number
+ width: 200px
+
span#processing-spinner
display: inline-block
margin-left: 30px
- height: 5px
+ height: 10px
p.image img
+image-box
@@ -59,4 +42,4 @@ p.image img
padding-top: 20px!important
img
display: block
- margin: 0 auto
+ margin: 0 auto
View
43 app/assets/stylesheets/partials/_registration.sass
@@ -0,0 +1,43 @@
+body.registration-payment
+ font-family: sans-serif
+ letter-spacing: initial
+
+ #content
+ margin-top: 3em
+ width: 550px
+ background-color: #fff
+ padding: 1.5em
+ +single-box-shadow(rgba(0,0,0,0.5), 0, 0, 1px)
+ +border-radius(2px)
+
+ h1
+ font-size: 2em
+ font-family: 'Folks'
+ .branded
+ color: $red
+ #payment-form
+ .submit
+ text-align: center
+ border-top: 1px solid #ddd
+ margin-top: 1.5em
+ padding-top: 1.5em
+ input.submit-button
+ +money-button
+ padding: 0.3em 1em
+ margin: 0
+ .billing-cycle, .email
+ display: inline-block
+ margin-bottom: 1em
+ .billing-cycle
+ label.billing-option
+ display: inline-block
+ margin-top: 0.25em
+ margin-right: 1.5em
+ margin-bottom: 0
+
+ .email
+ margin-right: 28px
+ input
+ width: 200px
+ footer
+ margin: 0
View
40 app/assets/stylesheets/partials/_settings.sass
@@ -1,33 +1,37 @@
form.edit_user
+ font-family: sans-serif
.field
margin-bottom: 5px
- label
- font-size: 1.1em
- margin-right: 5px
- input[type=text]
- padding: 2px 4px
- font-size: 1.1em
- margin: 2px
- color: #333
+ position: relative
input[type="checkbox"]
margin-right: 10px
a#cancel
float: right
line-height: 36px
+ font-size: 0.75em
span.cancel-notice
float: right
clear: right
- p#gravatar
- img
- +image-box
- vertical-align: middle
- margin-right: 10px
- padding: 3px
- a
- line-height: 38px
+ font-size: 0.75em
+ #user_contact_email
+ width: 100%
+ box-sizing: border-box
+ height: 32px
+ img.user-icon
+ position: absolute
+ bottom: 1px
+ right: 1px
+ +border-right-radius(3px)
.info
color: #888
font-style: italic
+ input.btn-small
+ font-size: 0.75em
+ #email-confirmed
+ color: $red
+ margin-left: 5px
+ &.confirmed
+ color: $green
.setting-panel
margin: 1em 0
@@ -117,3 +121,7 @@ img.sad_pinkie
color: #111
&:hover
cursor: default
+
+div.tooltip
+ font-family: sans-serif
+ font-size: 0.75em
View
6 app/controllers/application_controller.rb
@@ -14,8 +14,8 @@ class ApplicationController < ActionController::Base
private
def authenticate
- return if current_authorization
-
+ return if current_authorization
+
store_location
redirect_on_auth_failure
end
@@ -65,7 +65,7 @@ def store_location
end
def clear_location
- session[:return_to] = nil
+ session.delete(:return_to)
end
def redirect_back_or_default(default)
View
15 app/controllers/home_controller.rb
@@ -3,16 +3,13 @@ class HomeController < ApplicationController
skip_before_filter :authenticate_user, :except => [:library]
layout "landing", :except => [:contact, :archives, :library]
- def contact
- end
-
- def subscribe
- redirect_to registration_path
- end
-
def index
- if current_user.try(:status) == "active"
- return redirect_to library_path
+ if current_user
+ if current_user.status == "active"
+ return redirect_to library_path
+ elsif current_user.status != "disabled"
+ return redirect_to registration_path
+ end
end
@article_count = [Article.published.count / 10, "0+"].join
View
56 app/controllers/registration_controller.rb
@@ -1,15 +1,13 @@
class RegistrationController < ApplicationController
skip_before_filter :authenticate_user
- before_filter :ye_shall_not_pass, :except => [ :payment, :payment_pending,
- :create_payment, :complete ]
+ before_filter :ye_shall_not_pass, :except => [ :complete ]
def index
path = case current_user.status
- when "authorized" then {:action => :edit_profile }
- when "pending_confirmation" then {:action => :update_profile }
- when "confirmed" then {:action => :payment }
- when "payment_pending" then {:action => :payment }
- else library_path
+ when "authorized", "pending_confirmation", "confirmed", "payment_pending"
+ {:action => :payment }
+ else
+ library_path
end
redirect_to path
@@ -19,46 +17,11 @@ def restart
current_user.status = "authorized"
current_user.save
- redirect_to :action => :edit_profile
- end
-
- def edit_profile
- @user = current_user
- end
-
- def update_profile
- @user = current_user
-
- if params[:user]
- if @user.update_attributes(params[:user])
- @user.create_access_token
-
- RegistrationMailer.email_confirmation(@user).deliver
-
- @user.update_attribute(:status, "pending_confirmation")
- else
- render :edit_profile
- end
- end
- end
-
- def confirm_email
- user = User.find_by_access_token(params[:secret])
-
- return redirect_to(:action => :index) unless user
-
- user.clear_access_token
- user.update_attribute(:status, "confirmed")
-
- return redirect_to(:action => :payment)
- end
-
- def payment_pending
-
+ redirect_to :action => :payment
end
def payment
- unless current_user.status == "payment_pending" || current_user.status == "confirmed"
+ if current_user.status == "active"
redirect_to(:action => :complete)
end
end
@@ -78,9 +41,6 @@ def create_payment
end
end
- def complete
- end
-
def coupon_valid
payment_gateway = current_user.payment_gateway
@@ -93,7 +53,7 @@ def coupon_valid
# Called by ApplicationController#authenticate
def redirect_on_auth_failure
- redirect_to login_path
+ redirect_to login_path
end
def ye_shall_not_pass
View
4 app/controllers/sessions_controller.rb
@@ -28,7 +28,7 @@ def create
user.update_attribute(:status, "authorized")
authorization.update_attribute(:user_id, user.id)
- redirect_to registration_edit_profile_path
+ redirect_to registration_path
elsif authorization.user.status == "active"
redirect_back_or_default(library_path)
else
@@ -41,7 +41,7 @@ def destroy
clear_location
redirect_to "/"
end
-
+
def failure
@message = params[:message].humanize if params[:message]
end
View
25 app/controllers/user_email_controller.rb
@@ -0,0 +1,25 @@
+class UserEmailController < ApplicationController
+ def confirm
+ user = User.find_by_access_token(params[:secret])
+
+ if user
+ user.clear_access_token
+ user.update_attributes(:email_confirmed => true)
+ flash[:notice] = "Email address confirmed"
+ else
+ flash[:error] = "Sorry that confirmation link is out of date."
+ end
+
+ redirect_to user_settings_path
+ end
+
+ def change
+ @user = current_user
+
+ render :layout => false
+ end
+
+ def dismiss_warning
+ session[:dismiss_email_warning] = true
+ end
+end
View
33 app/controllers/users_controller.rb
@@ -1,6 +1,6 @@
class UsersController < ApplicationController
before_filter :find_user, :except => :show
- skip_before_filter :authenticate_user, :only => :destroy
+ skip_before_filter :authenticate_user, :only => [:destroy, :email_unique]
def show
@user = User.find_by_github_nickname(params[:id])
@@ -33,13 +33,20 @@ def update_credit_card
end
def update
- params[:current_page] ||= :edit
-
if @user.update_attributes(cleaned_params)
- flash[:notice] = "#{params[:current_page].humanize} settings updated!"
- redirect_to :action => params[:current_page]
+ session.delete(:dismiss_email_warning)
+ respond_to do |format|
+ format.html do
+ flash[:notice] = "#{params[:current_page].humanize} settings updated!"
+ redirect_to :action => params[:current_page]
+ end
+ format.js
+ end
else
- render :action => params[:current_page]
+ respond_to do |format|
+ format.html { render :action => params[:current_page] }
+ format.js
+ end
end
end
@@ -60,6 +67,20 @@ def destroy
AccountMailer.canceled(@user) unless @user.disabled?
end
+ def email_unique
+ email = params[:email] || (params[:user] && params[:user][:contact_email])
+ unique = User.where("id <> ? and contact_email = ?",
+ @user, email.downcase).empty?
+
+ message = if unique
+ true
+ else
+ { error: "This email is already registered" }
+ end
+
+ render :text => message.to_json
+ end
+
private
def find_user
View
8 app/decorators/user_decorator.rb
@@ -5,7 +5,7 @@ def member_since
h.l(user.created_at.to_date, :format => :long)
end
- def icon(size=32)
+ def icon(size=32, options={})
image_path = h.image_path("avatar.png")
unless user.contact_email.blank?
@@ -17,8 +17,8 @@ def icon(size=32)
# Manually set height / width so layouts don't collapse while gravatars are
# loading
#
- h.image_tag(image_path, :alt => user.name,
- :style => "width: #{size}px; height: #{size}px;", :class => "user-icon")
+ h.image_tag(image_path, options.merge(:alt => user.name,
+ :style => "width: #{size}px; height: #{size}px;", :class => "user-icon"))
end
def link_to_github
@@ -28,4 +28,4 @@ def link_to_github
def github_url
"https://github.com/#{user.github_nickname}"
end
-end
+end
View
7 app/helpers/application_helper.rb
@@ -19,6 +19,13 @@ def home_path
end
end
+ def show_email_warning?
+ current_user &&
+ current_user.active? &&
+ current_user.email_confirmed == false &&
+ session[:dismiss_email_warning] != true
+ end
+
def md(content)
MdPreview::Parser.parse(content)
end
View
3  app/models/payment_gateway/stripe.rb
@@ -54,7 +54,8 @@ def subscribe(params = {})
user.update_attributes(
:payment_provider => 'stripe',
- :payment_provider_id => customer.id
+ :payment_provider_id => customer.id,
+ :contact_email => params[:email]
)
user.enable
View
35 app/models/user.rb
@@ -3,6 +3,11 @@ class User < ActiveRecord::Base
active disabled}
ACTIVE_STATUSES = %w{active}
+ before_save :send_confirmation_email
+ before_create do
+ write_attribute(:share_token, SecureRandom.hex(5))
+ end
+
has_many :comments
has_many :subscriptions
has_many :payment_logs
@@ -17,24 +22,16 @@ class User < ActiveRecord::Base
# Email sanity check from Rails Docs
# http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000087
#
- validates_format_of :contact_email,
+ validates_format_of :contact_email,
:with => /\A\s*([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\Z/i,
:on => :update
attr_protected :admin, :status
- before_save do
- if changed.include?("contact_email")
- write_attribute(:contact_email, contact_email.strip.downcase)
- end
- end
-
- before_create do
- write_attribute(:share_token, SecureRandom.hex(5))
- end
-
def self.to_notify
- where(notifications_enabled: true, :status => ACTIVE_STATUSES)
+ where(:notifications_enabled => true,
+ :status => ACTIVE_STATUSES,
+ :email_confirmed => true)
end
def hashed_id
@@ -67,7 +64,6 @@ def disable
end
def enable(mailchimp_web_id=nil)
-
# TODO Move this to PaymentGateway
unless mailchimp_web_id.blank?
self.payment_provider_id = mailchimp_web_id
@@ -94,4 +90,17 @@ def create_access_token
def clear_access_token
update_attribute(:access_token, nil)
end
+
+ private
+
+ def send_confirmation_email
+ if changed.include?("contact_email")
+ write_attribute(:email_confirmed, false)
+ if contact_email.present?
+ write_attribute(:contact_email, contact_email.strip.downcase)
+ write_attribute(:access_token, SecureRandom.hex(10))
+ RegistrationMailer.email_confirmation(self).deliver
+ end
+ end
+ end
end
View
49 app/views/home/subscribe.html.haml
@@ -1,46 +1,5 @@
-#landing
- #banner
- %h2 Practicing Ruby: Becoming a subscriber
- = image_tag "ruby-divider.png"
+%h1 Redirecting to GitHub for authentication
- #subscribe
- #left
-
- %p
- So you've checked out our
- #{link_to "sample articles", sample_articles_path}
- and are ready to join Practicing Ruby?
- That's awesome! Here's what you'll
- get as a subscriber:
-
- %ul
- %li Immediate access to all our articles.
- %li Fresh content every two weeks.
- %li Conversations with Practicing Rubyists.
-
- %p
- By signing up, you'll also be supporting
- an independent publishing effort that is
- 100% reader-funded, and 100% reader-focused.
-
- #price
- %p A subscription costs only $8/month
-
- %p
- Click the big red button below to become a
- subscriber. We'll authorize you via Github,
- send you a confirmation email, and collect
- your payment information. Once
- that's taken care of, you'll be able to
- start enjoying Practicing Ruby right away.
-
- .subscribe
- = button_to "Shut up and take my money!".html_safe, registration_path,
- :method => :get
-
- #right
- = image_tag "preview.png", :id => "preview"
-
- %p{:style => "text-align: right"}
- If you have questions at all, don't hesitate to
- %a{:href => "/contact"} contact us
+%p
+ If you aren't redirected within 5 seconds, click
+ = link_to "here", login_path
View
5 app/views/layouts/application.html.haml
@@ -15,9 +15,8 @@
%body{class: "#{controller.controller_name}-#{controller.action_name}"}
= render :partial => "shared/admin_bar"
- - if active_broadcasts.any? && current_user.try(:status) == "active"
- #top-bar= render "shared/broadcasts"
-
+ = render :partial => "shared/broadcasts"
+ = render :partial => "user_email/warning"
= render :partial => "shared/flash", :locals => { :flash => flash }
= yield(:nudge)
View
1  app/views/layouts/landing.html.haml
@@ -4,6 +4,7 @@
%title Practicing Ruby | A unique journal curated by Gregory Brown
= stylesheet_link_tag 'application'
+ = javascript_include_tag 'application'
= csrf_meta_tag
= render :partial => "shared/ios_icon"
View
6 app/views/registration/confirm_email.html.haml
@@ -1,6 +0,0 @@
-- content_for(:header) { "Drat!" }
-
-%h3 The confirmation link you've clicked on is either expired or invalid.
-
-%p= link_to "Let's try this again", registration_edit_profile_path
-
View
12 app/views/registration/edit_profile.html.haml
@@ -1,12 +0,0 @@
-- content_for(:header) { "Registration" }
-- content_for(:title) { "Registration" }
-
-%h3 Hey #{@user.github_nickname}. Welcome to Practicing Ruby!
-
-%p Before you jump right in, we need some information about you, including where we should send you emails and if you want to be emailed at all.
-
-= form_for @user, :url => {:controller => :registration, :action => :update_profile} do |f|
- = render :partial => "users/form", :locals => {:f => f}
- %hr
- %p
- = f.submit "Continue"
View
39 app/views/registration/payment.html.haml
@@ -1,14 +1,15 @@
-- content_for(:header) { "Payment" }
-- content_for(:title) { "Payment" }
+- content_for(:title) { "Subscribe" }
- content_for :header_bottom do
= javascript_include_tag "https://js.stripe.com/v1/"
:coffeescript
- $ -> new PR.PaymentProcessor('#{STRIPE_PUBLISHABLE_KEY}');
+ $ -> new PR.PaymentProcessor('#{STRIPE_PUBLISHABLE_KEY}')
-%h3 You are so close. Just one last step.
+%h1
+ Subscribe to
+ %span.branded Practicing Ruby
%p
- All we need from you now is your payment information. We can either bill this
+ All we need is your payment information. We can either bill this
card $8.00 USD each month or once a year at $96.00 USD until you cancel your
account, which you can do at any time.
@@ -16,23 +17,29 @@
Your payment details are sent directly to Stripe: For security reasons, we do
not store your credit card number or other sensitive information.
-= form_tag registration_create_payment_path, :id => "payment-form" do
- = render 'shared/credit_card'
- .row
- %label Billing Cycle:
- .row
- = label_tag 'interval_month' do
+= form_tag registration_create_payment_path, :id => "payment-form",
+ 'parsley-validate' => '' do
+ .email
+ = label_tag :email, "Email Address"
+ = email_field_tag :email, current_user.contact_email,
+ :placeholder => 'you@yourdomain.com',
+ :required => true,
+ 'parsley-remote' => email_unique_users_path
+ .billing-cycle
+ %label Billing Cycle
+ = label_tag 'interval_month', :class => "billing-option" do
= radio_button_tag :interval, :month, true
Monthly - $8.00 USD
- = label_tag 'interval_year' do
+ = label_tag 'interval_year', :class => "billing-option" do
= radio_button_tag :interval, :year
Yearly - $96.00 USD
+ = render 'shared/credit_card'
- if params[:coupon].present?
- .row
+ .coupon
= label_tag :coupon, "Coupon Code"
= text_field_tag :coupon, params[:coupon]
- %hr
- %p
- = submit_tag "Submit Payment", :class => "submit-button"
+ .submit
+ = submit_tag "❤ Subscribe to Practicing Ruby",
+ :class => "submit-button"
%span#processing-spinner
View
8 app/views/registration/payment_pending.html.haml
@@ -1,8 +0,0 @@
-- content_for(:header) { "Payment" }
-- content_for(:title) { "Payment" }
-
-%p Right now Practicing Ruby is transitioning payment providers. So what does that mean for you?
-
-%p Until we are ready to make the switch, you get access to the full site free of charge. Once we start accepting payments again, we'll ask you to enter your payment information to continue using the service, but you won't be charged for accessing our content between now and then. Not a bad deal, right?
-
-%p Your account is now set up, which means that you can enjoy everything that Practicing Ruby has to offer. You can start by #{link_to 'browsing through our library', library_path} or reading a #{link_to 'randomly selected article', random_article_path}. If you have any questions, please send an email to #{link_to 'gregory@practicingruby.com', 'mailto:gregory@practicingruby.com'}.
View
11 app/views/registration/update_profile.html.haml
@@ -1,11 +0,0 @@
-- content_for(:header) { "Registration" }
-- content_for(:title) { "Registration" }
-
-Fantastic! We've sent a confirmation email to
-%strong #{@user.contact_email}.
-Check your
-email and click the link to continue setting up your account.
-
-If you don't receive that email within a few minutes you can always
-= link_to "go back", registration_edit_profile_path
-and try again.
View
4 app/views/registration_mailer/email_confirmation.text.erb
@@ -1,8 +1,8 @@
Hello,
-To confirm your Practicing Ruby subscription, click the link below.
+To confirm your Practicing Ruby email, click the link below.
-<%= registration_confirmation_url(:secret => @user.access_token) %>
+<%= confirm_email_url(:secret => @user.access_token) %>
If you run into problems, don't hesitate to contact gregory@practicingruby.com
View
14 app/views/shared/_broadcasts.html.haml
@@ -1,6 +1,8 @@
-#broadcasts
- = link_to "Dismiss", dismiss_broadcasts_path, :class => "dismiss",
- :remote => true
- - active_broadcasts.each do |broadcast|
- .broadcast
- = link_to broadcast.broadcast_message, broadcast.url
+- if active_broadcasts.any? && current_user.try(:status) == "active"
+ #top-bar
+ #broadcasts
+ = link_to "Dismiss", dismiss_broadcasts_path, :class => "dismiss",
+ :remote => true
+ - active_broadcasts.each do |broadcast|
+ .broadcast
+ = link_to broadcast.broadcast_message, broadcast.url
View
13 app/views/shared/_credit_card.html.haml
@@ -1,14 +1,15 @@
.payment-errors= @errors
-.row
+.cc-num
%label Card Number
- %input.card-number{type: "text", size: 20, autocomplete: "off"}
-.row
+ %input.card-number{type: "text", size: 20, autocomplete: "off",
+ placeholder: "4242-4242-4242-4242"}
+.cc-cvc
%label
CVC
%a#show-cvc-help{:href => "#cvc", :tabindex => -1} ?
- %input.card-cvc{type:"text", size: 4, autocomplete: "off"}
-.row
+ %input.card-cvc{type:"text", size: 4, autocomplete: "off", placeholder: "123"}
+.cc-exp
%label Card Expiration
= select_month nil, { add_month_numbers: true },
{ name: nil, id: nil, class: "card-expiry-month" }
@@ -20,4 +21,4 @@
%p
For added security, we verify your CVC, CVV, or CID code. This code isn't
stored and is checked before processing your payment.
- %p= image_tag "payment/cvc.gif"
+ %p= image_tag "payment/cvc.gif"
View
2  app/views/shared/_flash.html.haml
@@ -2,4 +2,4 @@
- flash.each do |flashtype, message|
%div{:class => "flash #{flashtype}", :id => "flash-#{flashtype}"}= message
:javascript
- setTimeout("$('#flash').fadeOut()", 4000);
+ setTimeout("$('#flash').slideUp()", 4000);
View
6 app/views/user_email/_warning.html.haml
@@ -0,0 +1,6 @@
+- if show_email_warning?
+ #email-confirmation-warning
+ Your email address isn't verified yet. Please check your inbox, or
+ = link_to "update your contact info", change_email_path,
+ :rel => "facebox"
+ to try again.
View
11 app/views/user_email/change.html.haml
@@ -0,0 +1,11 @@
+#change-email
+ = form_for @user, :remote => true, :html => {'parsley-validate' => ''} do |f|
+ = error_messages_for(@user)
+ .field
+ = f.label :contact_email, "Email Address"
+ = f.email_field :contact_email, :required => true,
+ 'parsley-remote' => email_unique_users_path
+ .field
+ = f.submit "Send confirmation email", :class => 'btn-small'
+:coffeescript
+ $('#edit_user_#{@user.id}').parsley()
View
1  app/views/user_email/dismiss_warning.js.coffee
@@ -0,0 +1 @@
+$("#email-confirmation-warning").fadeOut()
View
4 app/views/users/_settings_page.html.haml
@@ -3,7 +3,9 @@
- content_for :header_bottom do
= javascript_include_tag "https://js.stripe.com/v1/"
:coffeescript
- $ -> new PR.PaymentProcessor('#{STRIPE_PUBLISHABLE_KEY}')
+ $ ->
+ new PR.PaymentProcessor('#{STRIPE_PUBLISHABLE_KEY}')
+ $('img.user-icon[title]').tooltip()
$(document).on 'click', '#facebox .confirm-interval-change a', (e) ->
$(this).replaceWith('<div class="loading">')
View
23 app/views/users/notifications.html.haml
@@ -8,20 +8,25 @@
.info
All notifications will be sent to #{@user.contact_email}
%hr
- %p
+
+ = f.label :notify_updates do
= f.check_box :notify_updates
- = f.label :notify_updates, "Notify me about content and website updates"
- %p
+ Notify me about content and website updates
+
+ = f.label :notify_conversations do
= f.check_box :notify_conversations
- = f.label :notify_conversations, "Notify me when conversations start"
- %p
+ Notify me when conversations start
+
+ = f.label :notify_mentions do
= f.check_box :notify_mentions
- = f.label :notify_mentions, "Notify me when I am mentioned in a conversation"
- %p
+ Notify me when I am mentioned in a conversation
+
+ = f.label :notify_comment_made do
= f.check_box :notify_comment_made
- = f.label :notify_comment_made, "Notify me every time a comment is made"
+ Notify me every time a comment is made
+
%hr
%p
= f.submit "Update Settings"
-= render 'settings_page'
+= render 'settings_page'
View
15 app/views/users/profile.html.haml
@@ -7,15 +7,14 @@
= hidden_field_tag :current_page, :profile
= error_messages_for(@user)
.field
- = f.label :contact_email, "Email:"
+ = f.label :contact_email, "Email Address"
= f.text_field :contact_email
- %p#gravatar
- = UserDecorator.decorate(@user).icon(32)
- = link_to "Change your avatar at Gravatar.com", "http://gravatar.com/"
+ = UserDecorator.decorate(@user).icon(30,
+ :title => "Change your avatar at Gravatar.com")
%p
- = f.check_box :beta_tester
- = f.label :beta_tester,
- "Give me early access to experimental website features"
+ = f.label :beta_tester do
+ = f.check_box :beta_tester
+ Give me early access to experimental website features
%hr
%p
= f.submit "Update Settings"
@@ -24,4 +23,4 @@
:id => "cancel"
%span.cancel-notice.info Cancellation may take up to 24 hours
-= render 'settings_page'
+= render 'settings_page'
View
4 app/views/users/update.js.coffee
@@ -0,0 +1,4 @@
+unless <%= @user.errors.any? %>
+ $.facebox "<p>Email updated and confirmation sent. Thanks!</p>"
+else
+ alert "<%= @user.errors.full_messages.join(', ') %>"
View
1  config/application.rb
@@ -41,6 +41,7 @@ class Application < Rails::Application
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
+ config.i18n.enforce_available_locales = false
# JavaScript files you want as :defaults (application.js is always included).
# config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
View
12 config/routes.rb
@@ -83,6 +83,18 @@
post :change_billing_interval
post :mailchimp_yearly_billing
end
+ collection do
+ get :email_unique
+ end
+ end
+
+ controller :user_email do
+ get '/account/email/confirm/:secret', :action => 'confirm',
+ :as => 'confirm_email'
+ post '/account/email/dismiss_warning', :action => 'dismiss_warning',
+ :as => 'dismiss_email_warning'
+ get '/account/email/change', :action => 'change',
+ :as => 'change_email'
end
namespace :admin do
View
8 db/migrate/20131204201817_add_email_confirmed_to_users.rb
@@ -0,0 +1,8 @@
+class AddEmailConfirmedToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :email_confirmed, :boolean, :default => false,
+ :null => false
+ execute %{update users set email_confirmed = true where status in
+ ('active', 'confirmed', 'payment_pending')}
+ end
+end
View
3  db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20131114225709) do
+ActiveRecord::Schema.define(:version => 20131204201817) do
create_table "announcements", :force => true do |t|
t.text "title"
@@ -178,6 +178,7 @@
t.text "payment_provider"
t.text "payment_provider_id"
t.string "share_token"
+ t.boolean "email_confirmed", :default => false, :null => false
end
create_table "volumes", :force => true do |t|
View
5 test/factories/user_factory.rb
@@ -7,10 +7,15 @@
u.last_name 'Pepelio'
u.github_nickname 'frankpepelio'
u.notifications_enabled true
+ u.email_confirmed true
u.email { FactoryGirl.generate(:email) }
u.contact_email { FactoryGirl.generate(:email) }
u.payment_provider 'mailchimp'
u.payment_provider_id { FactoryGirl.generate(:payment_provider_id) }
u.status 'active'
+ before(:create) {
+ User.skip_callback(:save, :before, :send_confirmation_email) }
+ after(:create) {
+ User.set_callback(:save, :before, :send_confirmation_email) }
end
end
View
40 test/integration/registration_test.rb
@@ -1,41 +1,51 @@
require_relative "../test_helper"
class RegistrationTest < ActionDispatch::IntegrationTest
+ setup do
+ Capybara.current_driver = Capybara.javascript_driver
+ end
+
test "initiates the signup process" do
simulated_user.click_subscribe
end
test "successful registration" do
+ params = Support::SimulatedUser.default.merge(
+ :cc_number => "4242424242424242",
+ :cc_cvc => "123",
+ :cc_month => 1,
+ :cc_year => Date.today.year + 1
+ )
+
simulated_user
- .authenticate(:nickname => "TestUser", :uid => "12345")
- .create_profile(:email => "test@test.com")
- .confirm_email
- .make_payment
+ .authenticate(params)
+ .make_payment(params)
+
+ assert_current_path registration_complete_path
end
test "successful registration with extraneous whitespace in email" do
simulated_user
.authenticate(:nickname => "TestUser", :uid => "12345")
- .create_profile(:email => "test@test.com ")
- .confirm_email
- .make_payment
+ .make_db_payment(Support::SimulatedUser.default.merge(
+ :email => "test@test.com "))
end
test "failed registration due to missing email" do
simulated_user
.authenticate(:nickname => "TestUser", :uid => "12345")
- .create_profile
+ .make_stripe_payment
- assert_content "Contact email is invalid"
+ assert_content "This value is required."
end
test "failed registration due to invalid email" do
simulated_user
.authenticate(:nickname => "TestUser", :uid => "12345")
- .create_profile(:email => "Jordan dot Byron at Gmail dot com")
+ .make_stripe_payment(:email => "Jordan dot Byron at Gmail dot com")
- assert_content "Contact email is invalid"
+ assert_content "This value should be a valid email."
end
test "leaving registration process midstream" do
@@ -43,11 +53,10 @@ class RegistrationTest < ActionDispatch::IntegrationTest
simulated_user
.authenticate(user_params)
- .create_profile(:email => "test@test.com")
.logout
.authenticate(user_params)
- assert_current_path registration_update_profile_path
+ assert_current_path registration_payment_path
end
test "payment failure" do
@@ -67,10 +76,9 @@ class RegistrationTest < ActionDispatch::IntegrationTest
test "attempting to confirm twice" do
simulated_user
- .authenticate(:nickname => "TestUser", :uid => "12345")
- .create_profile(:email => "test@test.com")
+ .authenticate(Support::SimulatedUser.default)
+ .make_db_payment(Support::SimulatedUser.default)
.confirm_email(2)
- .make_payment
end
test "payment pending accounts" do
View
85 test/support/simulated_user.rb
@@ -13,9 +13,9 @@ def method_missing(*a, &b)
def self.default
{
- :nickname => "TestUser",
- :uid => "12345",
- :email => "test@test.com"
+ :nickname => "TestUser",
+ :uid => "12345",
+ :email => "test@test.com"
}
end
@@ -43,54 +43,69 @@ def authenticate(params)
def register(params)
authenticate(params)
- create_profile(params)
- confirm_email
- make_payment(params)
+ make_db_payment(params)
read_article
end
- def create_profile(params={})
- browser do
- visit registration_edit_profile_path
- fill_in "Email:", :with => params.fetch(:email, "")
- click_button "Continue"
- end
- end
-
def confirm_email(attempts=1)
- browser { assert_current_path registration_update_profile_path }
-
- mail = ActionMailer::Base.deliveries.pop
- base = Rails.application.routes.url_helpers.
- registration_confirmation_path(:secret => "")
+ # NOTE This method assumes a confirmation email is waiting in the queue.
+ mail = ActionMailer::Base.deliveries.pop
+ base = Rails.application.routes.url_helpers.
+ confirm_email_path(:secret => "")
secret = mail.body.to_s[/#{base}(\h+)/,1]
browser do
- attempts.times { visit registration_confirmation_path(:secret => secret) }
+ attempts.times { visit confirm_email_path(:secret => secret) }
end
end
def make_payment(params={})
- browser do
- assert_current_path registration_payment_path
+ if ENV['STRIPE'].present?
+ make_stripe_payment(params)
+ else
+ make_db_payment(params)
end
+ end
+
+ # Manual version of make_stripe_payment
+ #
+ def make_db_payment(params={})
+ browser { assert_current_path registration_payment_path }
- # TODO Find a way to test this through the UI
- #
@user.subscriptions.create(
:start_date => Date.today,
:payment_provider => "stripe",
:rate_cents => params.fetch(:billing_rate, 800),
:interval => params.fetch(:billing_interval, 'month'))
+ @user.email = params[:email]
@user.status = "active"
@user.save
+ browser { visit registration_complete_path }
+ end
+
+ def make_stripe_payment(params={})
browser do
- visit registration_complete_path
- #assert_current_path registration_complete_path
+ assert_current_path registration_payment_path
+
+ fill_in "Email Address", :with => params.fetch(:email, "")
+ choose "interval_#{params.fetch(:billing_interval, 'month')}"
+
+ find('input.card-number').set params.fetch(:cc_number, '')
+ find('input.card-cvc').set params.fetch(:cc_cvc, '')
+ find('select.card-expiry-month').set params.fetch(:cc_month, '')
+ find('select.card-expiry-year').set params.fetch(:cc_year, '')
+
+ click_button "❤ Subscribe to Practicing Ruby"
+
+ # Wait for ajax to complete
+ Capybara.default_wait_time = 10
+ assert has_no_css? "#processing-spinner.spinning"
end
+ ensure
+ Capybara.default_wait_time = 3
end
def read_article
@@ -133,9 +148,11 @@ def payment_failure
end
def cancel_account
+ ActionMailer::Base.deliveries.clear
+
browser do
- click_link "Settings"
- click_link "Unsubscribe from Practicing Ruby"
+ click_link "Settings"
+ click_link "Unsubscribe from Practicing Ruby"
assert_content "Sorry to see you go"
@@ -193,14 +210,14 @@ def change_billing_interval
def restart_registration
browser do
click_link "subscribing"
- assert_current_path registration_edit_profile_path
+ assert_current_path registration_payment_path
end
end
def edit_profile(params={})
browser do
click_link "Settings"
- fill_in "Email:", :with => params.fetch(:email, "")
+ fill_in "Email Address", :with => params.fetch(:email, "")
click_button "Update Settings"
end
end
@@ -222,10 +239,14 @@ def click_subscribe
})
browser do
- visit "/"
+ visit root_path
click_link "subscribe"
- assert_current_path "/registration/edit_profile"
+ # Redirect facebox
+ assert_content "Redirecting to GitHub"
+ click_link "here" # Don't wait the full 5 seconds
+
+ assert_current_path registration_payment_path
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.