jobshop 0.0.113 → 0.0.127

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/jobshop/application.js +5 -0
  3. data/app/assets/stylesheets/jobshop/application.scss +36 -53
  4. data/app/assets/stylesheets/jobshop/breakpoints.scss +13 -1
  5. data/app/assets/stylesheets/jobshop/dialog.scss +43 -0
  6. data/app/assets/stylesheets/jobshop/welcome.scss +0 -0
  7. data/app/controllers/concerns/registration_token_validation.rb +2 -2
  8. data/app/controllers/jobshop/application_controller.rb +5 -6
  9. data/app/controllers/jobshop/teams/lookups_controller.rb +1 -1
  10. data/app/controllers/jobshop/welcome/places_controller.rb +42 -0
  11. data/app/controllers/jobshop/{teams → welcome}/registrations_controller.rb +1 -2
  12. data/app/controllers/jobshop/welcome/things_controller.rb +42 -0
  13. data/app/controllers/jobshop/welcome_controller.rb +11 -0
  14. data/app/helpers/jobshop/application_helper.rb +8 -1
  15. data/app/models/jobshop/place.rb +9 -0
  16. data/app/models/jobshop/registration.rb +1 -1
  17. data/app/models/jobshop/team.rb +4 -1
  18. data/app/models/jobshop/thing.rb +9 -0
  19. data/app/models/jobshop/user.rb +5 -1
  20. data/app/views/devise/sessions/new.html.haml +12 -13
  21. data/app/views/jobshop/places/show.html.haml +11 -21
  22. data/app/views/jobshop/shared/_authenticated_header.html.haml +8 -0
  23. data/app/views/jobshop/shared/_unauthenticated_header.html.haml +10 -0
  24. data/app/views/jobshop/teams/lookups/show.html.haml +11 -12
  25. data/app/views/jobshop/welcome/index.html.haml +21 -0
  26. data/app/views/jobshop/welcome/places/new.html.haml +22 -0
  27. data/app/views/jobshop/welcome/registrations/new.html.haml +31 -0
  28. data/app/views/jobshop/welcome/things/new.html.haml +24 -0
  29. data/app/views/layouts/jobshop/unauthenticated.html.haml +1 -11
  30. data/config/initializers/simple_form.rb +5 -6
  31. data/config/routes.rb +35 -17
  32. data/db/migrate/20170311194758_initialize_jobshop.rb +41 -14
  33. data/lib/generators/jobshop/config/templates/config/initializers/jobshop.rb.tt +2 -0
  34. data/lib/generators/jobshop/dummy/dummy_generator.rb +1 -0
  35. data/lib/generators/jobshop/team/team_generator.rb +5 -4
  36. data/lib/jobshop/cli.rb +2 -2
  37. data/lib/jobshop/engine.rb +1 -1
  38. data/lib/jobshop/version.rb +1 -1
  39. metadata +31 -8
  40. data/app/assets/stylesheets/jobshop/static.scss +0 -41
  41. data/app/controllers/jobshop/setups_controller.rb +0 -8
  42. data/app/views/jobshop/setups/show.html.haml +0 -15
  43. data/app/views/jobshop/teams/registrations/new.html.haml +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 476798ce160b5d3e678f683a8e6dacaf0452721b
4
- data.tar.gz: 5407c737ed1626fe28be9a80e612bb3d2cab1c78
3
+ metadata.gz: a1f7f84dafc9184c1e6a5d3c954721d1675e2085
4
+ data.tar.gz: 3650939df1f73fa87ff01ea5e25bc21db5c710ee
5
5
  SHA512:
6
- metadata.gz: 7941c39baba3f7551cbfe5685ee4206dc1f49378407c5bf4ee90b2ff7f0822f6b2eaa79d6a321315a8e7451f5223278c994f0abe6aa11db25a03f60b69e8ffdd
7
- data.tar.gz: 9aa7ee6008822968c762c7281c53e67cee9eab378c313db218485778be20090e02fe34de16d1f9da94f706b505bfec264e8fb74586d95359a80ca5253b2322cb
6
+ metadata.gz: e7a9c6d76e362b90315654764525adddbe8aded05806ca0cbc54ee4ecea7a63c00550795c4c6668e3096890afd7a278373141036e56243060e22dcf41102d43b
7
+ data.tar.gz: ee9dde1678245104fa8803969fd53ffcc9433b4db143b9188b207ea2356d2198c1f0f661e4720077e78cb37e9111873af03fc649b86b0eed87c7cea27d37be4a
@@ -27,3 +27,8 @@ $(document).on("click", "li.mdl-menu__item", function(event) {
27
27
  menuItemLink.get(0).click();
28
28
  }
29
29
  });
30
+
31
+ $(document).on("click", "ul.select-users-list li", function(event) {
32
+ var userCheckBox = $(this).find("input[type=checkbox]").first();
33
+ userCheckBox.get(0).click();
34
+ });
@@ -1,74 +1,57 @@
1
- @charset "utf-8"
1
+ @charset "utf-8";
2
2
 
3
3
  @import "material";
4
-
5
4
  @import "jobshop/breakpoints";
6
- @import "jobshop/static";
5
+ @import "jobshop/dialog";
6
+ @import "jobshop/welcome";
7
7
 
8
8
  html, body {
9
9
  background-color: unquote("rgb(#{$palette-indigo-50})");
10
10
  font-family: "Roboto", "Helvetica", sans-serif;
11
+ margin: 0;
12
+ padding: 0;
11
13
  }
12
14
 
13
- html { height: 100%; }
14
- body { min-height: 100%; margin: 0 auto; }
15
-
16
15
  main {
17
- display: flex;
18
- justify-content: center;
19
- }
20
-
21
- #sign-in,
22
- #register {
23
- @extend .mdl-shadow--2dp;
24
-
25
- width: 100%;
26
-
27
- @include tablet-up {
28
- width: 440px;
29
- }
30
-
31
- .mdl-card__supporting-text {
32
- width: auto;
33
- }
16
+ padding-top: 0px;
34
17
 
35
- .mdl-textfield {
36
- width: 100%;
37
- }
18
+ @include larger-than-handset-p { padding-top: 24px; }
19
+ @include larger-than-handset-l { padding-top: 48px; }
38
20
  }
39
21
 
40
- .mdl-layout-title {
41
- text-decoration: none;
42
- }
43
-
44
- .mdl-menu__item a {
45
- color: inherit;
46
- font-weight: inherit;
47
- text-decoration: none;
22
+ .mdl-textfield {
23
+ width: 100%;
48
24
  }
49
25
 
50
- .mdl-card__title {
51
- align-items: center;
52
- justify-content: space-around;
26
+ .static-header {
27
+ @extend .mdl-color--white;
28
+ @extend .mdl-color-text--black;
53
29
 
54
- h4 {
55
- opacity: 0.82;
56
- text-align: right;
30
+ z-index: 50;
31
+ margin: 0 !important;
57
32
 
58
- strong {
59
- font-size: 1.1em;
60
- }
33
+ .mdl-layout-title {
34
+ color: rgba(0, 0, 0, 0.87) !important;
35
+ font-weight: 700;
36
+ opacity: 0.87;
37
+ text-decoration: none;
61
38
  }
62
- }
63
39
 
64
- .mdl-card__actions {
65
- align-items: center;
66
- display: flex;
67
- justify-content: space-between;
68
- padding: $card-vertical-padding $card-horizontal-padding;
69
-
70
- button {
71
- @extend .mdl-button;
72
- @extend .mdl-button--colored;
40
+ .mdl-navigation {
41
+ .mdl-navigation__link {
42
+ @extend .mdl-typography--text-uppercase;
43
+
44
+ background-color: transparent !important;
45
+ border-bottom: 4px solid transparent;
46
+ color: rgba(0, 0, 0, 0.87);
47
+ display: inline-block;
48
+ font-weight: 700;
49
+ height: 60px;
50
+ line-height: 68px;
51
+
52
+ &:hover {
53
+ border-bottom: 4px solid unquote("rgb(#{$palette-indigo-500})");
54
+ }
55
+ }
73
56
  }
74
57
  }
@@ -23,7 +23,19 @@ $breakpoints: (
23
23
  );
24
24
 
25
25
  @mixin handset {
26
- @media screen and (max-width: #{map-get($breakpoints, xl-handset-p)}) {
26
+ @media screen and (max-width: #{map-get($breakpoints, xl-handset-l)}) {
27
+ @content;
28
+ }
29
+ }
30
+
31
+ @mixin larger-than-handset-l {
32
+ @media screen and (min-width: #{map-get($breakpoints, xl-handset-l)}) {
33
+ @content;
34
+ }
35
+ }
36
+
37
+ @mixin larger-than-handset-p {
38
+ @media screen and (min-width: #{map-get($breakpoints, xl-handset-p)}) {
27
39
  @content;
28
40
  }
29
41
  }
@@ -0,0 +1,43 @@
1
+ .dialog-box {
2
+ @extend .mdl-grid;
3
+ @extend .mdl-shadow--2dp;
4
+
5
+ background: $card-background-color;
6
+ max-width: 480px;
7
+ }
8
+
9
+ .dialog-box .dialog-box__header {
10
+ @extend .mdl-cell;
11
+ @extend .mdl-cell--4-col;
12
+ @extend .mdl-color-text--white;
13
+
14
+ align-items: flex-start;
15
+ background-color: unquote("rgb(#{$palette-indigo-500})");
16
+ display: flex;
17
+ justify-content: center;
18
+
19
+ @include handset {
20
+ display: none;
21
+ }
22
+
23
+ & > .material-icons {
24
+ font-size: 10rem;
25
+ margin-top: 1rem;
26
+ }
27
+ }
28
+
29
+ .dialog-box .dialog-box__content {
30
+ @extend .mdl-cell;
31
+ @extend .mdl-cell--12-col;
32
+ @extend .mdl-card;
33
+ }
34
+
35
+ .dialog-box .dialog-box__header + .dialog-box__content {
36
+ @extend .mdl-cell--8-col;
37
+ }
38
+
39
+
40
+ .dialog-box .dialog-box__content .mdl-card__actions {
41
+ display: flex;
42
+ justify-content: space-between;
43
+ }
File without changes
@@ -4,12 +4,12 @@ class RegistrationTokenValidation
4
4
  end
5
5
 
6
6
  def initialize(controller)
7
- @controller = controller.dup
7
+ @controller = controller
8
8
  @token = @controller.params.fetch(:registration_token, nil)
9
9
  @team_id = @controller.params.fetch(:team_id, nil)
10
10
 
11
11
  if @token
12
- @controller.redirect_to(@controller.new_user_session_path) unless valid?
12
+ @controller.redirect_to(@controller.new_user_session_path(team_id: @team_id)) unless valid?
13
13
  end
14
14
  end
15
15
 
@@ -4,8 +4,6 @@
4
4
 
5
5
  module Jobshop
6
6
  class ApplicationController < ActionController::Base
7
- include Pundit
8
-
9
7
  layout :layout_for_application
10
8
 
11
9
  protect_from_forgery
@@ -13,12 +11,9 @@ module Jobshop
13
11
  before_action EmailTokenValidation
14
12
  before_action :authenticate_user!
15
13
 
16
- # after_action :verify_authorized, except: :index
17
- # after_action :verify_policy_scoped, only: :index
18
-
19
14
  def after_sign_in_path_for(resource_or_scope)
20
15
  if !resource_or_scope.onboard?
21
- team_setup_path
16
+ welcome_path
22
17
  else
23
18
  super
24
19
  end
@@ -26,6 +21,10 @@ module Jobshop
26
21
 
27
22
  private
28
23
 
24
+ def current_team
25
+ @current_team ||= current_user && current_user.team
26
+ end; helper_method :current_team
27
+
29
28
  def layout_for_application
30
29
  if devise_controller? && controller_name == "sessions" ||
31
30
  controller_path == "jobshop/teams/lookups"
@@ -11,7 +11,7 @@ module Jobshop
11
11
  def create
12
12
  emails = params[:user][:email].split(",").map(&:strip).take(5)
13
13
  Jobshop::Team.grouped_by_email(emails).each_pair do |email, teams|
14
- Jobshop::TeamsMailer.found_teams(email, teams).deliver_later
14
+ Jobshop::TeamsMailer.found_teams(email, teams).deliver_now
15
15
  end
16
16
 
17
17
  redirect_to teams_lookup_path
@@ -0,0 +1,42 @@
1
+ require_dependency "jobshop/application_controller"
2
+
3
+ module Jobshop
4
+ class Welcome::PlacesController < ApplicationController
5
+ respond_to :html
6
+
7
+ def new
8
+ @place = if session[:place_id].present?
9
+ current_team.places.find_by(id: session[:place_id])
10
+ end
11
+ @place ||= current_team.places.build
12
+ respond_with(@place)
13
+ end
14
+
15
+ def index
16
+ redirect_to new_welcome_places_path
17
+ end
18
+
19
+ def create
20
+ if @place = current_team.places.create(place_params)
21
+ session[:place_id] = @place.id
22
+ session[:next_onboard_path] = "/"
23
+ end
24
+
25
+ respond_with @place, location: -> { "/" }
26
+ end
27
+
28
+ def update
29
+ @place = current_team.places.find(params[:id])
30
+ if @place.update(place_params)
31
+ session[:place_id] = @place.id
32
+ respond_with @place, location: -> { "/" }
33
+ else
34
+ render :new
35
+ end
36
+ end
37
+
38
+ private def place_params
39
+ params.require(:place).permit(:name)
40
+ end
41
+ end
42
+ end
@@ -1,7 +1,7 @@
1
1
  require_dependency "jobshop/application_controller"
2
2
 
3
3
  module Jobshop
4
- class Teams::RegistrationsController < ApplicationController
4
+ class Welcome::RegistrationsController < ApplicationController
5
5
  skip_before_action :authenticate_user!
6
6
 
7
7
  before_action RegistrationTokenValidation
@@ -23,4 +23,3 @@ module Jobshop
23
23
  end
24
24
  end
25
25
  end
26
-
@@ -0,0 +1,42 @@
1
+ require_dependency "jobshop/application_controller"
2
+
3
+ module Jobshop
4
+ class Welcome::ThingsController < ApplicationController
5
+ respond_to :html
6
+
7
+ def new
8
+ @thing = if session[:thing_id].present?
9
+ current_team.things.find_by(id: session[:thing_id])
10
+ end
11
+ @thing ||= current_team.things.build
12
+ respond_with(@thing)
13
+ end
14
+
15
+ def index
16
+ redirect_to new_welcome_things_path
17
+ end
18
+
19
+ def create
20
+ if @thing = current_team.things.create(thing_params)
21
+ session[:thing_id] = @thing.id
22
+ session[:next_onboard_path] = new_welcome_places_path
23
+ end
24
+
25
+ respond_with @thing, location: -> { new_welcome_places_path }
26
+ end
27
+
28
+ def update
29
+ @thing = current_team.things.find(params[:id])
30
+ if @thing.update(thing_params)
31
+ session[:thing_id] = @thing.id
32
+ respond_with @thing, location: -> { new_welcome_places_path }
33
+ else
34
+ render :new
35
+ end
36
+ end
37
+
38
+ private def thing_params
39
+ params.require(:thing).permit(:identifier)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,11 @@
1
+ require_dependency "jobshop/application_controller"
2
+
3
+ module Jobshop
4
+ class WelcomeController < ApplicationController
5
+ def index
6
+ if session[:next_onboard_path].present?
7
+ redirect_to session[:next_onboard_path]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,7 +1,14 @@
1
1
  module Jobshop
2
2
  module ApplicationHelper
3
3
  def body_class
4
- @body_class ||= "#{controller_name}-#{action_name}"
4
+ @body_class ||= [
5
+ controller_class,
6
+ "#{controller_name}__#{action_name}"
7
+ ].flatten.uniq.compact
8
+ end
9
+
10
+ private def controller_class
11
+ @controller_class = controller_path.split("/").reject { |i| i == "jobshop" }
5
12
  end
6
13
  end
7
14
  end
@@ -0,0 +1,9 @@
1
+ module Jobshop
2
+ class Place < ApplicationRecord
3
+ belongs_to :team
4
+
5
+ validates(:name,
6
+ uniqueness: { scope: :team_id, case_sensitive: false },
7
+ presence: true)
8
+ end
9
+ end
@@ -39,7 +39,7 @@ module Jobshop
39
39
 
40
40
  def user_params
41
41
  registration_params.fetch(:user, ActionController::Parameters.new)
42
- .permit(:email, :password, :password_confirmation)
42
+ .permit(:forename, :surname, :email, :password, :password_confirmation)
43
43
  end
44
44
  end
45
45
  end
@@ -1,7 +1,9 @@
1
1
  module Jobshop
2
2
  class Team < ApplicationRecord
3
3
  belongs_to :owner, class_name: "Jobshop::User", optional: true
4
- has_many :users, class_name: "Jobshop::User"
4
+ has_many :users
5
+ has_many :things
6
+ has_many :places
5
7
  has_one :default_dashboard, class_name: "Jobshop::Dashboard"
6
8
 
7
9
  scope :grouped_by_email, ->(email_addresses) {
@@ -15,6 +17,7 @@ module Jobshop
15
17
  }
16
18
 
17
19
  def generate_registration_token
20
+ return false if owner_id
18
21
  raw, encrypted = Devise.token_generator.generate(
19
22
  self.class, :registration_token)
20
23
 
@@ -0,0 +1,9 @@
1
+ module Jobshop
2
+ class Thing < ApplicationRecord
3
+ belongs_to :team
4
+
5
+ validates(:identifier,
6
+ uniqueness: { scope: :team_id, case_sensitive: false },
7
+ presence: true)
8
+ end
9
+ end
@@ -4,7 +4,7 @@ module Jobshop
4
4
  # :confirmable, :lockable, :timeoutable and :omniauthable
5
5
  devise :database_authenticatable, :recoverable, :rememberable
6
6
 
7
- belongs_to :team, optional: true
7
+ belongs_to :team
8
8
  has_one :default_dashboard, class_name: "Jobshop::Dashboard", through: :team
9
9
  has_many :session_activations, dependent: :destroy
10
10
 
@@ -27,6 +27,10 @@ module Jobshop
27
27
  false
28
28
  end
29
29
 
30
+ def full_name
31
+ @full_name = [ forename, surname ].join(" ")
32
+ end
33
+
30
34
  def activate_session
31
35
  session_activations.activate(SecureRandom.hex).activation_token
32
36
  end
@@ -1,16 +1,15 @@
1
1
  %main.mdl-layout__content
2
- %div.mdl-grid
3
- %div.mdl-cell.mdl-cell--12-col
4
- %div#sign-in.mdl-card
5
- = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
6
- .mdl-card__title(style="height: 200px;")
7
- %object.logo{ type: "image/svg+xml", data: image_path("jobshop/logo.svg") }
2
+ .dialog-box(style="max-width: 400px;")
3
+ .dialog-box__content
4
+ = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
5
+ .mdl-card__title
6
+ %object.logo{ type: "image/svg+xml", data: image_path("jobshop/logo.svg"), style: "margin: 0 auto;" }
8
7
 
9
- .mdl-card__supporting-text
10
- = f.input(:email)
11
- = f.input(:password)
12
- = f.input(:team_id, as: :hidden, input_html: { value: params[:team_id] })
8
+ .mdl-card__supporting-text
9
+ = f.input(:email)
10
+ = f.input(:password)
11
+ = f.input(:team_id, as: :hidden, input_html: { value: params[:team_id] })
13
12
 
14
- .mdl-card__actions
15
- = f.input :remember_me, as: :boolean, input_html: { class: "mdl-checkbox__input" }
16
- = f.button :submit, "Log in"
13
+ .mdl-card__actions
14
+ = f.input :remember_me, as: :boolean, input_html: { class: "mdl-checkbox__input" }
15
+ = f.button :submit, "Log in"
@@ -1,22 +1,12 @@
1
- %div.mdl-layout.mdl-js-layout.mdl-layout--fixed-header
2
- %header.mdl-layout__header.mdl-layout__header--waterfall
3
- %div.mdl-layout__header-row
4
- %a.mdl-layout-title(href="#{jobshop.root_path}") Jobshop
5
- %div.mdl-layout-spacer
6
- %div.static-navigation-container
7
- %nav.mdl-navigation
8
- %a.mdl-navigation__link{ href: jobshop.about_path }
9
- About
10
- %a.mdl-navigation__link.mdl-color-text--pink{ href: jobshop.teams_lookup_path }
11
- Sign In
12
-
13
- %main
14
- %div
15
- You are currently logged in at these stations:
16
- %ul
1
+ %div.mdl-layout.mdl-js-layout
2
+ = render partial: "jobshop/shared/authenticated_header"
3
+ %main.mdl-layout__content
4
+ .mdl-cell.mdl-cell--12-col
5
+ %p You are currently logged in at these stations:
6
+ %table.mdl-data-table.mdl-js-data-table.mdl-data-table--selectable.mdl-shadow--2dp
17
7
  - session_activations.each do |s|
18
- %li{ id: s.id }
19
- %p
20
- = s.id
21
- = "(current)" if s.activation_token == session[:activation_token]
22
- %p= link_to "log this out", revoke_session_path(s), remote: true, method: :delete, data: { confirm: "Are you sure you want to log out of the selected session?" }
8
+ %tbody
9
+ %tr
10
+ %td= s.id
11
+ %td= "(current)" if s.activation_token == session[:activation_token]
12
+ %td= link_to "log this out", revoke_session_path(s), remote: true, method: :delete, data: { confirm: "Are you sure you want to log out of the selected session?" }
@@ -0,0 +1,8 @@
1
+ %header.static-header.mdl-layout__header.mdl-layout__header--waterfall
2
+ %div.mdl-layout__header-row
3
+ %a.mdl-layout-title(href="#{jobshop.root_path}") Jobshop
4
+ %div.mdl-layout-spacer
5
+ %div.static-navigation-container
6
+ %nav.mdl-navigation
7
+ = link_to destroy_user_session_path, method: :delete, class: %w(mdl-navigation__link mdl-color-text--pink) do
8
+ Sign out
@@ -0,0 +1,10 @@
1
+ %header.static-header.mdl-layout__header.mdl-layout__header--waterfall
2
+ %div.mdl-layout__header-row
3
+ %a.mdl-layout-title(href="#{jobshop.root_path}") Jobshop
4
+ %div.mdl-layout-spacer
5
+ %div.static-navigation-container
6
+ %nav.mdl-navigation
7
+ %a.mdl-navigation__link{ href: jobshop.about_path }
8
+ About
9
+ %a.mdl-navigation__link.mdl-color-text--pink{ href: jobshop.teams_lookup_path }
10
+ Sign In
@@ -1,15 +1,14 @@
1
1
  %main.mdl-layout__content
2
- %div.mdl-grid
3
- %div.mdl-cell.mdl-cell--12-col
4
- %div#sign-in.mdl-card
5
- = simple_form_for @lookup, url: teams_lookup_path do |f|
6
- .mdl-card__title
7
- %h4 Can't find your team?
2
+ .dialog-box
3
+ .dialog-box__content
4
+ = simple_form_for @lookup, url: teams_lookup_path do |f|
5
+ .mdl-card__title
6
+ %h4 Can't find your team?
8
7
 
9
- .mdl-card__supporting-text
10
- %p Enter your email address and we'll send you the link.
11
- = f.input :email
8
+ .mdl-card__supporting-text
9
+ %p Enter your email address and we'll send you the link.
10
+ = f.input :email
12
11
 
13
- .mdl-card__actions
14
- .expand
15
- = f.button :submit, "Send the link to my email!"
12
+ .mdl-card__actions
13
+ .expand
14
+ = f.button :submit, "Send the link to my email!"
@@ -0,0 +1,21 @@
1
+ .mdl-layout.mdl-js-layout.mdl-layout--fixed-header
2
+ = render partial: "jobshop/shared/authenticated_header"
3
+ %main.mdl-layout__content
4
+ .dialog-box
5
+ .dialog-box__header
6
+ %i.material-icons location_city
7
+ .dialog-box__content
8
+ .mdl-card__title
9
+ %h4 Welcome Aboard
10
+
11
+ .mdl-card__supporting-text
12
+ %p
13
+ To get you up and running quickly, let's walk through making
14
+ your first product together. We'll track this order as it
15
+ moves around the shop to discover exactly where time is being
16
+ spent.
17
+
18
+ .mdl-card__actions
19
+ .expand
20
+ %a.mdl-button.mdl-button--primary.mdl-button--raised{ href: new_welcome_things_path }
21
+ Get started &raquo;
@@ -0,0 +1,22 @@
1
+ .mdl-layout.mdl-js-layout.mdl-layout--fixed-header
2
+ = render partial: "jobshop/shared/authenticated_header"
3
+ %main.mdl-layout__content
4
+ .dialog-box
5
+ .dialog-box__header
6
+ %i.material-icons location_city
7
+ .dialog-box__content
8
+ = simple_form_for @place, url: welcome_places_path do |f|
9
+ .mdl-card__title
10
+ %h4 First Place
11
+
12
+ .mdl-card__supporting-text
13
+ %p
14
+ Places are how Jobshop manages all the different locations in your
15
+ shop that a product may visit during its lifetime.
16
+
17
+ = hidden_field_tag(:id, @place.id, id: nil) if @place.persisted?
18
+ = f.input :name, label: "Where are you?", as: :string
19
+
20
+ .mdl-card__actions
21
+ %a.mdl-button.mdl-button--raised.mdl-button--secondary{ href: new_welcome_things_path } &laquo; Back
22
+ = f.button :submit, raw("Next &raquo;")
@@ -0,0 +1,31 @@
1
+ %main.mdl-layout__content
2
+ .dialog.dialog--no-back.mdl-grid.mdl-grid--no-spacing.mdl-shadow--2dp
3
+ %header.onboard__avatar.mdl-cell.mdl-cell--4-col
4
+ %i.material-icons location_city
5
+ .mdl-card.mdl-cell.mdl-cell--8-col
6
+ = simple_form_for @registration, url: welcome_registration_path do |f|
7
+ .mdl-card__title
8
+ %h4.mdl-card__title-text
9
+ Registration
10
+ .mdl-card__supporting-text
11
+ = hidden_field_tag(:team_id, params[:team_id])
12
+ = hidden_field_tag(:registration_token, params[:registration_token])
13
+ = f.fields_for(f.object.user) do |uf|
14
+ = uf.input :forename, label: "First Name"
15
+ = uf.input :surname, label: "Last Name"
16
+ = uf.input :email
17
+
18
+ = f.fields_for(f.object.team) do |tf|
19
+ %p
20
+ Choose a name for your team.
21
+ = tf.input :name, label: "Team Name"
22
+
23
+ = f.fields_for(f.object.user) do |uf|
24
+ %p
25
+ Pick a strong password. Ideally it will be longer than eight
26
+ characters, have mixed upper/lower case, and include numbers
27
+ and symbols.
28
+ = uf.input :password
29
+ = uf.input :password_confirmation
30
+ .mdl-card__actions
31
+ = f.button :submit, "Next &#187;".html_safe
@@ -0,0 +1,24 @@
1
+ .mdl-layout.mdl-js-layout.mdl-layout--fixed-header
2
+ = render partial: "jobshop/shared/authenticated_header"
3
+ %main.mdl-layout__content
4
+ .dialog-box
5
+ .dialog-box__header
6
+ %i.material-icons location_city
7
+ .dialog-box__content
8
+ = simple_form_for @thing, url: welcome_things_path do |f|
9
+ .mdl-card__supporting-text
10
+ %h4 First Product
11
+
12
+ %p
13
+ How do you identify this particular product? You can enter an
14
+ <strong>SKU</strong>, a <strong>Part Number</strong> or any other
15
+ unique identifier your team may be using. We'll use this to look
16
+ up the product again in the future.
17
+
18
+ = hidden_field_tag(:id, @thing.id, id: nil) if @thing.persisted?
19
+ = f.input :identifier, label: "Product Identifier", as: :string
20
+
21
+ .mdl-card__actions
22
+ .expand
23
+ = f.button :submit, raw("Next &raquo;")
24
+
@@ -1,16 +1,6 @@
1
1
  = content_for(:body) do
2
2
  %div.mdl-layout.mdl-js-layout.mdl-layout--fixed-header
3
- %header.mdl-layout__header.mdl-layout__header--waterfall
4
- %div.mdl-layout__header-row
5
- %a.mdl-layout-title(href="#{jobshop.root_path}") Jobshop
6
- %div.mdl-layout-spacer
7
- %div.static-navigation-container
8
- %nav.mdl-navigation
9
- %a.mdl-navigation__link{ href: jobshop.about_path }
10
- About
11
- %a.mdl-navigation__link.mdl-color-text--pink{ href: jobshop.teams_lookup_path }
12
- Sign In
13
-
3
+ = render partial: "jobshop/shared/unauthenticated_header"
14
4
  = yield
15
5
 
16
6
  = render template: "layouts/jobshop/application"
@@ -6,7 +6,8 @@ SimpleForm.setup do |config|
6
6
  # stack. The options given below are used to wrap the
7
7
  # whole input.
8
8
 
9
- config.wrappers :string, class: [ "mdl-textfield", "mdl-js-textfield" ],
9
+ config.wrappers :string,
10
+ class: %w(mdl-textfield mdl-js-textfield),
10
11
  error_class: "is-invalid" do |b|
11
12
 
12
13
  b.use :html5
@@ -143,11 +144,9 @@ SimpleForm.setup do |config|
143
144
  # config.input_class = nil
144
145
 
145
146
  # Define the default class of the input wrapper of the boolean input.
146
- config.boolean_label_class = [
147
- "mdl-checkbox",
148
- " mdl-js-checkbox",
149
- " mdl-js-ripple-effect"
150
- ]
147
+ config.boolean_label_class = %w(mdl-checkbox
148
+ mdl-js-checkbox
149
+ mdl-js-ripple-effect)
151
150
 
152
151
  # Defines if the default input wrapper class should be included in radio
153
152
  # collection wrappers.
data/config/routes.rb CHANGED
@@ -1,27 +1,45 @@
1
1
  Jobshop::Engine.routes.draw do
2
- devise_for(:users, class_name: "Jobshop::User", module: "devise", skip: [ :sessions ])
2
+ devise_for :users, class_name: "Jobshop::User", module: "devise",
3
+ skip: :sessions
3
4
 
4
- devise_scope :user do
5
- get "teams/:team_id/sign_in" => "sessions#new", as: :new_user_session
6
- post "teams/:team_id/sign_in" => "sessions#create", as: :user_session
7
- delete "/sign_out" => "sessions#destroy", as: :destroy_user_session
8
- delete "/revoke(/:id)" => "session_activations#destroy", as: :revoke_session
9
- end
5
+ unauthenticated do
6
+ devise_scope :user do
7
+ get "/sign_in/:team_id", to: "sessions#new", as: :new_user_session
8
+ post "/sign_in/:team_id", to: "sessions#create", as: :user_session
9
+ end
10
+
11
+ get "/teams/lookup", to: "teams/lookups#show", as: :teams_lookup
12
+ post "/teams/lookup", to: "teams/lookups#create"
10
13
 
11
- resources :teams, only: [ ] do
12
- collection do
13
- resource :lookup, only: [ :show, :create ], module: "teams",
14
- as: "teams_lookup"
14
+ namespace :welcome do
15
+ resource :registration, except: %w(edit destroy show update)
15
16
  end
16
17
 
17
- resource :registration, only: [ :new, :create ],
18
- controller: "teams/registrations"
18
+ get "/", to: "dashboards#show"
19
19
  end
20
20
 
21
- get "/setup" => "setups#show", as: :team_setup
22
- get "/places" => "places#show"
21
+ authenticated do
22
+ devise_scope :user do
23
+ delete "/sign_out", to: "sessions#destroy", as: :destroy_user_session
24
+ delete "/revoke/:id",
25
+ to: "session_activations#destroy", as: :revoke_session
26
+ end
23
27
 
24
- get "/about" => redirect("https://github.com/jobshop/jobshop"), as: :about
28
+ namespace :welcome do
29
+ resource :things, except: %w(destroy show) do
30
+ get "/", action: :index
31
+ end
25
32
 
26
- root to: "dashboards#show"
33
+ resource :places, except: %w(destroy show) do
34
+ get "/", action: :index
35
+ end
36
+ end
37
+
38
+ get "/welcome", to: "welcome#index"
39
+ get "/logins", to: "places#show"
40
+
41
+ root to: "dashboards#show"
42
+ end
43
+
44
+ get "/about" => redirect("https://github.com/jobshop/jobshop"), as: :about
27
45
  end
@@ -1,9 +1,10 @@
1
1
  class InitializeJobshop < ActiveRecord::Migration[5.0]
2
2
  def change
3
- enable_extension "pgcrypto" unless extension_enabled?("pgcrypto")
3
+ enable_extension "pgcrypto" unless extension_enabled? "pgcrypto"
4
+ enable_extension "citext" unless extension_enabled? "citext"
4
5
 
5
- create_table :jobshop_users,
6
- id: :uuid, default: -> { "gen_random_uuid()" } do |t|
6
+ create_table(:jobshop_users, id: :uuid,
7
+ default: -> { "gen_random_uuid()" }) do |t|
7
8
  ## Database authenticatable
8
9
  t.string :email, null: false, default: ""
9
10
  t.string :encrypted_password, null: false, default: ""
@@ -33,10 +34,12 @@ class InitializeJobshop < ActiveRecord::Migration[5.0]
33
34
  # t.string :unlock_token # Only if unlock strategy is :email or :both
34
35
  # t.datetime :locked_at
35
36
 
36
- t.uuid :team_id
37
+ t.uuid :team_id, null: false
38
+ t.string :forename
39
+ t.string :surname
37
40
  t.string :email_authentication_token
38
41
  t.datetime :email_authentication_token_sent_at, :datetime
39
- t.timestamps null: false
42
+ t.timestamps
40
43
  end
41
44
 
42
45
  add_index :jobshop_users, [ :email, :team_id ], unique: true
@@ -44,8 +47,8 @@ class InitializeJobshop < ActiveRecord::Migration[5.0]
44
47
  # add_index :jobshop_users, :confirmation_token, unique: true
45
48
  # add_index :jobshop_users, :unlock_token, unique: true
46
49
 
47
- create_table :jobshop_teams,
48
- id: :uuid, default: -> { "gen_random_uuid()" } do |t|
50
+ create_table(:jobshop_teams, id: :uuid,
51
+ default: -> { "gen_random_uuid()" }) do |t|
49
52
  t.string :name
50
53
  t.uuid :owner_id
51
54
  t.string :registration_token
@@ -53,6 +56,11 @@ class InitializeJobshop < ActiveRecord::Migration[5.0]
53
56
  t.timestamps
54
57
  end
55
58
 
59
+ add_check :jobshop_teams, <<~CHECK, name: :check_owner_id_on_jobshop_teams
60
+ ((owner_id IS NULL AND registration_token IS NOT NULL AND registration_token_sent_at IS NOT NULL) OR
61
+ (owner_id IS NOT NULL AND registration_token IS NULL AND registration_token_sent_at IS NULL))
62
+ CHECK
63
+
56
64
  add_index :jobshop_teams, [ "registration_token" ], unique: true
57
65
 
58
66
  add_foreign_key :jobshop_users,
@@ -61,8 +69,8 @@ class InitializeJobshop < ActiveRecord::Migration[5.0]
61
69
  add_foreign_key :jobshop_teams,
62
70
  :jobshop_users, column: "owner_id", on_delete: :restrict
63
71
 
64
- create_table :jobshop_dashboards,
65
- id: :uuid, default: -> { "gen_random_uuid()" } do |t|
72
+ create_table(:jobshop_dashboards, id: :uuid,
73
+ default: -> { "gen_random_uuid()" }) do |t|
66
74
  t.uuid :team_id
67
75
  t.timestamps
68
76
  end
@@ -70,15 +78,34 @@ class InitializeJobshop < ActiveRecord::Migration[5.0]
70
78
  add_foreign_key :jobshop_dashboards,
71
79
  :jobshop_teams, column: "team_id", on_delete: :cascade
72
80
 
73
- create_table :jobshop_session_activations,
74
- id: :uuid, default: -> { "gen_random_uuid()" } do |t|
75
- t.uuid :user_id, null: false
76
- t.string :activation_token
77
-
81
+ create_table(:jobshop_session_activations, id: :uuid,
82
+ default: -> { "gen_random_uuid()" }) do |t|
83
+ t.uuid :user_id, null: false
84
+ t.string :activation_token, null: false
78
85
  t.timestamps
79
86
  end
80
87
 
81
88
  add_index :jobshop_session_activations, :user_id
82
89
  add_index :jobshop_session_activations, :activation_token, unique: true
90
+
91
+ create_table(:jobshop_things, id: :uuid,
92
+ default: -> { "gen_random_uuid()" }) do |t|
93
+ t.uuid :team_id, null: false
94
+ t.citext :identifier
95
+
96
+ t.timestamps
97
+ end
98
+
99
+ add_index :jobshop_things, [ :identifier, :team_id ], unique: true
100
+
101
+ create_table(:jobshop_places, id: :uuid,
102
+ default: -> { "gen_random_uuid()" }) do |t|
103
+ t.uuid :team_id, null: false
104
+ t.citext :name
105
+
106
+ t.timestamps
107
+ end
108
+
109
+ add_index :jobshop_places, [ :name, :team_id ], unique: true
83
110
  end
84
111
  end
@@ -1,2 +1,4 @@
1
1
  Jobshop.configure do |config|
2
+ # URL of the redis db maintaining session data.
3
+ #config.session_store_url = ENV.fetch("REDIS_URL") if Rails.env.production?
2
4
  end
@@ -90,6 +90,7 @@ module Jobshop
90
90
  bundle_command("exec rails db:create")
91
91
  bundle_command("exec rails db:migrate")
92
92
  bundle_command("exec rails db:test:load_schema")
93
+ bundle_command("exec rails g jobshop:team")
93
94
  end
94
95
 
95
96
  protected
@@ -16,7 +16,7 @@ module Jobshop
16
16
  end
17
17
 
18
18
  def create_team
19
- @team = ::Jobshop::Team.create!
19
+ @team = ::Jobshop::Team.new
20
20
  end
21
21
 
22
22
  def generate_token
@@ -30,10 +30,11 @@ module Jobshop
30
30
  # protocol. HTTPS isn't mandatory in production but it is very, VERY
31
31
  # highly recommended.
32
32
  @secure_url = ::Jobshop::Engine.routes.url_helpers.
33
- new_team_registration_url(@team,
33
+ new_welcome_registration_url(
34
34
  protocol: link_protocol,
35
35
  host: link_host,
36
- registration_token: @token
36
+ registration_token: @token,
37
+ team_id: @team.id
37
38
  )
38
39
  end
39
40
 
@@ -46,7 +47,7 @@ module Jobshop
46
47
 
47
48
  #{@secure_url}
48
49
 
49
- This link is valid for 30 minutes from now and will expire at:
50
+ This link is valid for 30 minutes and will expire at:
50
51
  #{30.minutes.from_now.in_time_zone("Eastern Time (US & Canada)")}
51
52
 
52
53
  Thank you for using Jobshop!
data/lib/jobshop/cli.rb CHANGED
@@ -20,10 +20,10 @@ module Jobshop
20
20
 
21
21
  class Dummy < Thor
22
22
  desc "server", "Start the dummy app"
23
- default_command def server
23
+ default_command def server(*args)
24
24
  if Jobshop::DummyApp.exist?
25
25
  Dir.chdir(Jobshop::DummyApp.path)
26
- exec("rails s")
26
+ exec %Q(rails s #{args.join(" ")})
27
27
  else
28
28
  abort <<~MESSAGE
29
29
  Dummy app does not exist at #{Jobshop::DummyApp.path}
@@ -1,4 +1,5 @@
1
1
  require "action_view"
2
+ require "postgresql/check"
2
3
  require "devise"
3
4
  require "haml-rails"
4
5
  require "jquery-rails"
@@ -42,7 +43,6 @@ module Jobshop
42
43
  Warden::Manager.after_set_user except: :fetch do |user, warden, opts|
43
44
  SessionActivation.deactivate(warden.raw_session["activation_token"])
44
45
  warden.raw_session["activation_token"] = user.activate_session
45
- #warden.cookies.permanent.encrypted[:_jobshop_place_id]
46
46
  end
47
47
 
48
48
  Warden::Manager.after_fetch do |user, warden, opts|
@@ -6,7 +6,7 @@ module Jobshop
6
6
  module VERSION
7
7
  MAJOR = 0
8
8
  MINOR = 0
9
- TINY = 113
9
+ TINY = 127
10
10
  PRE = nil
11
11
 
12
12
  CODE_NAME = "bump it up prime".freeze
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jobshop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.113
4
+ version: 0.0.127
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank J. Mattia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-11 00:00:00.000000000 Z
11
+ date: 2017-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coffee-rails
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 1.3.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: postgresql-check
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.1.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.1.2
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: pundit
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -335,7 +349,8 @@ files:
335
349
  - app/assets/javascripts/jobshop/application.js
336
350
  - app/assets/stylesheets/jobshop/application.scss
337
351
  - app/assets/stylesheets/jobshop/breakpoints.scss
338
- - app/assets/stylesheets/jobshop/static.scss
352
+ - app/assets/stylesheets/jobshop/dialog.scss
353
+ - app/assets/stylesheets/jobshop/welcome.scss
339
354
  - app/controllers/concerns/email_token_validation.rb
340
355
  - app/controllers/concerns/registration_token_validation.rb
341
356
  - app/controllers/jobshop/application_controller.rb
@@ -343,19 +358,23 @@ files:
343
358
  - app/controllers/jobshop/places_controller.rb
344
359
  - app/controllers/jobshop/session_activations_controller.rb
345
360
  - app/controllers/jobshop/sessions_controller.rb
346
- - app/controllers/jobshop/setups_controller.rb
347
361
  - app/controllers/jobshop/teams/lookups_controller.rb
348
- - app/controllers/jobshop/teams/registrations_controller.rb
349
362
  - app/controllers/jobshop/teams_controller.rb
363
+ - app/controllers/jobshop/welcome/places_controller.rb
364
+ - app/controllers/jobshop/welcome/registrations_controller.rb
365
+ - app/controllers/jobshop/welcome/things_controller.rb
366
+ - app/controllers/jobshop/welcome_controller.rb
350
367
  - app/helpers/jobshop/application_helper.rb
351
368
  - app/jobs/jobshop/application_job.rb
352
369
  - app/mailers/jobshop/application_mailer.rb
353
370
  - app/mailers/jobshop/teams_mailer.rb
354
371
  - app/models/jobshop/application_record.rb
355
372
  - app/models/jobshop/dashboard.rb
373
+ - app/models/jobshop/place.rb
356
374
  - app/models/jobshop/registration.rb
357
375
  - app/models/jobshop/session_activation.rb
358
376
  - app/models/jobshop/team.rb
377
+ - app/models/jobshop/thing.rb
359
378
  - app/models/jobshop/user.rb
360
379
  - app/models/jobshop/virtual_record.rb
361
380
  - app/policies/jobshop/application_policy.rb
@@ -374,11 +393,15 @@ files:
374
393
  - app/views/jobshop/dashboards/show.html.haml
375
394
  - app/views/jobshop/places/show.html.haml
376
395
  - app/views/jobshop/session_activations/destroy.js.erb
377
- - app/views/jobshop/setups/show.html.haml
396
+ - app/views/jobshop/shared/_authenticated_header.html.haml
397
+ - app/views/jobshop/shared/_unauthenticated_header.html.haml
378
398
  - app/views/jobshop/teams/lookups/show.html.haml
379
- - app/views/jobshop/teams/registrations/new.html.haml
380
399
  - app/views/jobshop/teams_mailer/found_teams.html.haml
381
400
  - app/views/jobshop/teams_mailer/found_teams.text.erb
401
+ - app/views/jobshop/welcome/index.html.haml
402
+ - app/views/jobshop/welcome/places/new.html.haml
403
+ - app/views/jobshop/welcome/registrations/new.html.haml
404
+ - app/views/jobshop/welcome/things/new.html.haml
382
405
  - app/views/layouts/jobshop/application.html.haml
383
406
  - app/views/layouts/jobshop/mailer.text.erb
384
407
  - app/views/layouts/jobshop/unauthenticated.html.haml
@@ -430,7 +453,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
430
453
  version: '0'
431
454
  requirements: []
432
455
  rubyforge_project:
433
- rubygems_version: 2.6.10
456
+ rubygems_version: 2.6.8
434
457
  signing_key:
435
458
  specification_version: 4
436
459
  summary: An open source Manufacturing Execution System.
@@ -1,41 +0,0 @@
1
- body.sessions-new,
2
- body.lookups-show,
3
- body.registrations-new,
4
- body.registrations-create {
5
- header {
6
- @extend .mdl-color--white;
7
- @extend .mdl-color-text--black;
8
-
9
- z-index: 50;
10
- margin: 0 !important;
11
-
12
- .mdl-layout-title {
13
- color: rgba(0, 0, 0, 0.87) !important;
14
- opacity: 0.87;
15
- font-weight: 700;
16
- }
17
-
18
- .mdl-navigation {
19
- .mdl-navigation__link {
20
- @extend .mdl-typography--text-uppercase;
21
-
22
- background-color: transparent !important;
23
- border-bottom: 4px solid transparent;
24
- color: rgba(0, 0, 0, 0.87);
25
- display: inline-block;
26
- font-weight: 700;
27
- height: 60px;
28
- line-height: 68px;
29
-
30
- &:hover {
31
- border-bottom: 4px solid unquote("rgb(#{$palette-indigo-500})");
32
- }
33
- }
34
- }
35
- }
36
-
37
- .mdl-layout__content {
38
- display: flex;
39
- justify-content: center;
40
- }
41
- }
@@ -1,8 +0,0 @@
1
- require_dependency "jobshop/application_controller"
2
-
3
- module Jobshop
4
- class SetupsController < ApplicationController
5
- def show
6
- end
7
- end
8
- end
@@ -1,15 +0,0 @@
1
- %div.mdl-layout.mdl-js-layout.mdl-layout--fixed-header
2
- %header.mdl-layout__header.mdl-layout__header--waterfall
3
- %div.mdl-layout__header-row
4
- %a.mdl-layout-title(href="#{jobshop.root_path}") Jobshop
5
- %div.mdl-layout-spacer
6
- %div.static-navigation-container
7
- %nav.mdl-navigation
8
- %a.mdl-navigation__link{ href: jobshop.about_path }
9
- About
10
- %a.mdl-navigation__link.mdl-color-text--pink{ href: jobshop.teams_lookup_path }
11
- Sign In
12
-
13
- %main
14
- %div(class="mdl-grid demo-content")
15
- Welcome Aboard
@@ -1,30 +0,0 @@
1
- %main.mdl-layout__content
2
- %div.mdl-grid
3
- %div.mdl-cell.mdl-cell--12-col
4
- %div#register.mdl-card
5
- = simple_form_for @registration,
6
- url: team_registration_path(@registration.team) do |f|
7
- .mdl-card__title
8
- %object.logo{ type: "image/svg+xml",
9
- data: image_path("jobshop/logo.svg") }
10
- %h4
11
- Get started with
12
- %br>
13
- %strong Jobshop
14
- .mdl-card__supporting-text
15
- = hidden_field_tag(:registration_token, params[:registration_token])
16
- = f.fields_for(f.object.team) do |tf|
17
- %p
18
- Pick a name for your team. It will usually be (or include) your
19
- company's name.
20
- = tf.input :name, label: "Team Name"
21
-
22
- = f.fields_for(f.object.user) do |uf|
23
- = uf.input :email
24
- %p
25
- Pick a strong password. Ideally it will be longer than eight
26
- characters, have mixed upper/lower case, and include numbers
27
- and symbols.
28
- = uf.input :password
29
- = uf.input :password_confirmation
30
- = f.button :submit, "Next &#187;".html_safe