mr_common 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -9
  3. data/app/controllers/mr_common/dashboard_controller.rb +1 -0
  4. data/app/controllers/mr_common/pre_registrations/import_controller.rb +26 -0
  5. data/app/controllers/mr_common/pre_registrations/pre_registrations_controller.rb +48 -0
  6. data/app/controllers/mr_common/registrations/confirmations_controller.rb +54 -0
  7. data/app/controllers/mr_common/registrations/export_controller.rb +35 -4
  8. data/app/controllers/mr_common/registrations/public_controller.rb +24 -1
  9. data/app/controllers/mr_common/registrations/registrations_controller.rb +2 -0
  10. data/app/controllers/mr_common/registrations/resend_notifications_controller.rb +27 -0
  11. data/app/controllers/mr_common/registrations/success_controller.rb +1 -0
  12. data/app/mailers/mr_common/registration_mailer.rb +13 -3
  13. data/app/models/concerns/mr_common/csv_exportable.rb +6 -2
  14. data/app/models/mr_common/country.rb +9 -0
  15. data/app/models/mr_common/csv_renderer.rb +38 -0
  16. data/app/models/mr_common/pattern.rb +10 -0
  17. data/app/models/mr_common/pre_registration.rb +52 -0
  18. data/app/models/mr_common/pre_registration_importer.rb +91 -0
  19. data/app/models/mr_common/registration.rb +11 -2
  20. data/app/models/mr_common/registration_decorator.rb +20 -0
  21. data/app/models/mr_common/reminder.rb +6 -0
  22. data/app/models/mr_common/timezone.rb +5 -0
  23. data/app/views/layouts/mr_common/_navigation.html.erb +7 -0
  24. data/app/views/mr_common/pre_registrations/import/new.html.erb +27 -0
  25. data/app/views/mr_common/pre_registrations/pre_registrations/_form.html.erb +29 -0
  26. data/app/views/mr_common/pre_registrations/pre_registrations/index.html.erb +32 -0
  27. data/{lib/generators/mr_common/views/templates/mr_common/registrations/public → app/views/mr_common/pre_registrations/pre_registrations}/new.html.erb +2 -1
  28. data/{lib/generators/mr_common/views/templates/mr_common/registration_mailer/confirmation.html.erb → app/views/mr_common/registration_mailer/confirmed_registration.html.erb} +3 -0
  29. data/app/views/mr_common/registration_mailer/{confirmation.text.erb → confirmed_registration.text.erb} +2 -0
  30. data/app/views/mr_common/registration_mailer/revoked_registration.html.erb +5 -0
  31. data/app/views/mr_common/registration_mailer/revoked_registration.text.erb +6 -0
  32. data/app/views/mr_common/registration_mailer/unconfirmed_registration.html.erb +3 -0
  33. data/app/views/mr_common/registration_mailer/unconfirmed_registration.text.erb +4 -0
  34. data/app/views/mr_common/registrations/registrations/_form.html.erb +6 -0
  35. data/app/views/mr_common/registrations/registrations/index.html.erb +4 -0
  36. data/app/views/mr_common/registrations/registrations/show.html.erb +34 -3
  37. data/config/routes.rb +13 -1
  38. data/db/migrate/20190522151523_create_pre_registrations.rb +12 -0
  39. data/db/migrate/20190530220614_add_confirmed_to_registrations.rb +5 -0
  40. data/lib/generators/mr_common/controllers/USAGE +9 -0
  41. data/lib/generators/mr_common/controllers/controllers_generator.rb +24 -0
  42. data/lib/generators/mr_common/mailers/USAGE +16 -0
  43. data/lib/generators/mr_common/mailers/mailers_generator.rb +31 -0
  44. data/lib/generators/mr_common/models/USAGE +8 -0
  45. data/lib/generators/mr_common/models/models_generator.rb +23 -0
  46. data/lib/generators/mr_common/views/USAGE +1 -4
  47. data/lib/generators/mr_common/views/views_generator.rb +7 -8
  48. data/lib/mr_common.rb +14 -2
  49. data/lib/mr_common/version.rb +1 -1
  50. data/spec/dummy/config/initializers/mr_common.rb +10 -0
  51. data/spec/dummy/db/schema.rb +14 -4
  52. data/spec/dummy/log/development.log +1969 -50958
  53. data/spec/dummy/log/test.log +647 -14437
  54. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/-G/-gyHBJNssgJVEwk7sD1F9nK_dT1D1R5NjlAvcESJxME.cache +1 -0
  55. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/3d/3DI5adLgOKj6Q76fFxrLy_hKYkrlht0MEWksNkYgoXk.cache +1 -0
  56. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/3n/3NK03OBfUwDPKVYV6_11AOo3IGNUQPed4dqB-Ddv6-Q.cache +0 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7d/7DbSMErmkAd0cL6q_WyLMfx7gLYQB0h9smKZ0xGjOjY.cache +2 -0
  58. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/9m/9MK1AG2zx0HVFVRULpQQnQQY0HiNroKnYEz2FUBzw6k.cache +1 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/CO/cojbSP1f9tYoCyycggqXgW3OYb3atOkOpf-h-Axn36o.cache +1 -0
  60. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/H6/h6GO-mL-rzWHmoiFw-uuwdny5zj_zSb1Xzun5jkza5k.cache +1 -0
  61. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/LG/LG2fyButWQgZoNvH4qD2dBMQhl35dSysUbxsvw_2PA0.cache +0 -0
  62. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/LZ/lZ_yNthdvXeWexZW9TZuMWs6lZBLMEF5rp7aA5Bl9uU.cache +2 -0
  63. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Lh/LhBzrZLgp6fzHcgWFt_B-TqHzHaPyKv9Uk67brUycZE.cache +1 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/N-/N-7bXt4Ck0q2VWzHHuRPKL0a0CH_1W_eyAU59tZraxE.cache +1 -0
  65. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Pi/PI3-Xhs-TBYEqgGeCf3hWGn6vj1qCdqBqgYRY2jNr4Q.cache +1 -0
  66. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QQ/qqtvJX4CrLCTv4jjIg0LwoNxJUyDpLus0RIbg6D6muk.cache +1 -0
  67. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Se/setx2SgK-JX4zwjgbekiVnvWJnbp0-2XnO0TQJfCOHU.cache +1 -0
  68. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ue/UecY7KKAHkP6oiqpx1zkXAL9WWGqWdhgqhixQyJ82is.cache +1 -0
  69. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/VF/VfH7y0fTeHy8aLBK_bu5OKprS0c1-GCHUzrMzp-O0_I.cache +1 -0
  70. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/WC/WCV-7lpbJsFHZueodYuN5542p56UPNtby1LVPUjvuJU.cache +1 -0
  71. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/WV/wvfl97Gci1mpw00EFHWc0Q9x0DLBar03PTd9BrZ7MF0.cache +1 -0
  72. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/eP/ePjuIgA1bwfnJWATz7q6EjGfNnpvYEaKW_2aozCJFBQ.cache +1 -0
  73. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ge/GegLYwRNBYKvfYR1-WWnx3ssHerCO6M94VPxwUTYHtM.cache +1 -0
  74. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/h9/H95rpLdUakuAt6lpkUhlZvxsYlSb-VrfpiznZYmWnGo.cache +1 -0
  75. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/p_/P_kWJKPROj6qDVkyzNQgx8wZmba8YaTqtWMQyGHFtuY.cache +1 -0
  76. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/w3/w34qFl6hbbodwCEAr9Px_jzso7svjewL5BaOf6QMIN4.cache +1 -0
  77. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/wi/wi_OyVKzMxj9CcM3e2Bhab5PQSg4wh0nD-7QJQ3SVPw.cache +1 -0
  78. data/spec/dummy/tmp/pids/server.pid +1 -1
  79. data/spec/factories/registrations.rb +5 -5
  80. data/spec/models/registration_spec.rb +40 -0
  81. metadata +80 -11
  82. data/app/helpers/mr_common/form_helper.rb +0 -11
  83. data/app/models/mr_common/csv_exporter.rb +0 -35
  84. data/app/models/mr_common/default_csv_export_adapter.rb +0 -23
  85. data/app/views/mr_common/registration_mailer/confirmation.html.erb +0 -4
  86. data/lib/generators/mr_common/views/templates/mr_common/registration_mailer/confirmation.text.erb +0 -6
  87. data/lib/generators/mr_common/views/templates/mr_common/registrations/success/index.html.erb +0 -2
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module MrCommon
6
+ # Renders collections of objects to a CSV string.
7
+ #
8
+ # @param collection [Enumerable<Object>] a collection of objects.
9
+ # @param fields [Array<String, Symbol>] fields to use as columns in the CSV.
10
+ # It is expected that each item responds to each field if sent as a message.
11
+ # @param decorator [SimpleDelegator, nil] optional wrapper class used to
12
+ # generate values for CSV rows when items in the collection don't respond to
13
+ # every field provided.
14
+ class CSVRenderer
15
+ attr_reader :collection, :decorator, :fields
16
+
17
+ def initialize(collection: [], fields: [], decorator: nil)
18
+ @collection = collection
19
+ @decorator = decorator
20
+ @fields = fields.map(&:to_sym)
21
+ end
22
+
23
+ # Renders the provided collection to a CSV string.
24
+ #
25
+ # @return [String] the CSV file contents
26
+ def render
27
+ CSV.generate do |csv|
28
+ csv << fields
29
+
30
+ collection.each do |item|
31
+ item = decorator ? decorator.new(item) : item
32
+ row = fields.map { |field_name| item.try(field_name) }
33
+ csv << row
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,11 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MrCommon
4
+ # Common regex patterns
4
5
  class Pattern
5
6
  class << self
7
+ # Matches valid email addresses. Ensures that the @ symbol is
8
+ # preceded by something resembling a username and followed by something
9
+ # resembling a domain name.
6
10
  def email
7
11
  /\A\S+@\S+\.\S+\z/
8
12
  end
13
+
14
+ # Matches most phone numbers a person could enter. Tests for at least 7 of
15
+ # the characters that can appear in a phone number.
16
+ def phone_number
17
+ /[\d()\\x+\- ]{7,}/
18
+ end
9
19
  end
10
20
  end
11
21
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MrCommon
4
+ # When MrCommon.enable_pre_registration is true newly created Registrations
5
+ # will be checked against existing PreRegistrations. If a PreRegistration is
6
+ # found the Registration will be confirmed automatically.
7
+ #
8
+ # A PreRegistration requires a first+last name OR an email to be valid.
9
+ class PreRegistration < ApplicationRecord
10
+ validates :email, presence: true, format: MrCommon::Pattern.email, if: :should_validate_email?
11
+ validates :first_name, presence: true, if: :should_validate_name?
12
+ validates :last_name, presence: true, if: :should_validate_name?
13
+ validates :normalized_name, presence: true, if: :should_validate_name?
14
+
15
+ before_validation :normalize_email
16
+ before_validation :set_normalized_name
17
+
18
+ def self.exists_for?(registration)
19
+ email = registration.email&.downcase
20
+ name = normalized_name_from(registration).presence
21
+
22
+ exists_for_email = email.present? ? exists?(email: email) : false
23
+ exists_for_name = name.present? ? exists?(normalized_name: name) : false
24
+ exists_for_email || exists_for_name
25
+ end
26
+
27
+ private
28
+
29
+ def self.normalized_name_from(registration_or_self)
30
+ first = registration_or_self.first_name.downcase.gsub(/\W+/, "-")
31
+ last = registration_or_self.last_name.downcase.gsub(/\W+/, "-")
32
+ spacer = first.present? && last.present? ? "-" : ""
33
+ "#{first}#{spacer}#{last}"
34
+ end
35
+
36
+ def should_validate_email?
37
+ first_name.blank? && last_name.blank?
38
+ end
39
+
40
+ def should_validate_name?
41
+ email.blank?
42
+ end
43
+
44
+ def set_normalized_name
45
+ self.normalized_name = PreRegistration.normalized_name_from(self)
46
+ end
47
+
48
+ def normalize_email
49
+ self.email = email.downcase
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MrCommon
4
+ # Imports PreRegistration records from a CSV file that looks like:
5
+ #
6
+ # first_name, last_name, email
7
+ # John, Smith, jsmith@example.com
8
+ # Jane, Doe, jdoe@example.com
9
+ # ,,unknown@example.com
10
+ # Name,Only,
11
+ #
12
+ # @author Corey Smedstad <csmedstad@mreach.com>
13
+ class PreRegistrationImporter
14
+ RowStruct = Struct.new(:first_name, :last_name, :email) do
15
+ # Returns true if the row does not represent a duplicate PreRegistration
16
+ # @return [Boolean]
17
+ def new?
18
+ !PreRegistration.exists_for?(self)
19
+ end
20
+ end
21
+
22
+ ResultStruct = Struct.new(:created, :skipped)
23
+
24
+ attr_reader :csv, :current_row, :result
25
+
26
+ # @param csv [String] a the contents of a CSV file
27
+ def initialize(csv)
28
+ @csv = csv
29
+ @created = 0
30
+ @skipped = 0
31
+ end
32
+
33
+ # Attempts to create PreRegistration records for each row in the CSV
34
+ # recording the number of created and skipped records in the process.
35
+ #
36
+ # The result is memoized to prevent double imports.
37
+ #
38
+ # @returns [ResultStruct] the result of the import
39
+ def import
40
+ @result ||= perform_import
41
+ end
42
+
43
+ private
44
+
45
+ def perform_import
46
+ PreRegistration.transaction do
47
+ rows.each do |data|
48
+ load_row(data)
49
+
50
+ if current_row.new?
51
+ make_pre_registration
52
+ else
53
+ skip_pre_registration
54
+ end
55
+ end
56
+ end
57
+
58
+ ResultStruct.new(@created, @skipped)
59
+ end
60
+
61
+ def rows
62
+ CSV.parse(csv, headers: true)
63
+ end
64
+
65
+ def load_row(data)
66
+ first_name = data[0] || ""
67
+ last_name = data[1] || ""
68
+ email = data[2] || ""
69
+
70
+ @current_row = RowStruct.new(first_name, last_name, email)
71
+ end
72
+
73
+ def make_pre_registration
74
+ pre_registration = PreRegistration.new(
75
+ first_name: current_row.first_name,
76
+ last_name: current_row.last_name,
77
+ email: current_row.email
78
+ )
79
+
80
+ if pre_registration.save
81
+ @created += 1
82
+ else
83
+ skip_pre_registration
84
+ end
85
+ end
86
+
87
+ def skip_pre_registration
88
+ @skipped += 1
89
+ end
90
+ end
91
+ end
@@ -8,11 +8,20 @@ module MrCommon
8
8
 
9
9
  validates :first_name, presence: true
10
10
  validates :last_name, presence: true
11
- validates :email, presence: true
11
+ validates :email, presence: true, format: Pattern.email
12
12
  validates :company_name, presence: true
13
- validates :telephone, presence: true
13
+ validates :telephone, presence: true, format: Pattern.phone_number
14
14
  validates :job_title, presence: true
15
15
  validates :contact_via_email, inclusion: [true, false]
16
16
  validates :contact_via_phone, inclusion: [true, false]
17
+
18
+ validate :telephone_length
19
+
20
+ private
21
+
22
+ def telephone_length
23
+ digits = telephone.gsub(/\D+/, "")
24
+ errors.add(:telephone, "must be longer") if digits.length < 7
25
+ end
17
26
  end
18
27
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MrCommon
4
+ # Implements custom field values when exporting Registration records as CSV.
5
+ class RegistrationDecorator < SimpleDelegator
6
+ def preregistered?
7
+ __getobj__.preregistered? ? "YES" : "NO"
8
+ end
9
+
10
+ def confirmed?
11
+ __getobj__.confirmed? ? "YES" : "NO"
12
+ end
13
+
14
+ if MrCommon.registration_confirmation_strategy == :pre_register
15
+ def pre_registered?
16
+ PreRegistration.exists_for?(__getobj__) ? "YES" : "NO"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,6 +3,10 @@
3
3
  require_dependency "mr_common/application_record"
4
4
 
5
5
  module MrCommon
6
+ # Stores information for generating downloadable calendar reminders that
7
+ # appear in some emails.
8
+ #
9
+ # When
6
10
  class Reminder < ApplicationRecord
7
11
  validates :start_time, presence: true
8
12
  validates :end_time, presence: true
@@ -15,6 +19,8 @@ module MrCommon
15
19
 
16
20
  before_validation :parameterize_slug
17
21
 
22
+ scope :for_confirmed_notice, -> { where(include_in_confirmation_mailer: true) }
23
+
18
24
  def to_ical
19
25
  cal = Icalendar::Calendar.new
20
26
 
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MrCommon
4
+ # Helper class for building time zone select options. Pares down the list by
5
+ # grouping locations that share ones or offsets.
4
6
  class Timezone
5
7
  class << self
8
+ # @return [Array<String>] list of time zone names ordered by offset
6
9
  def time_zone_options
7
10
  filtered_time_zones.collect(&:name)
8
11
  end
9
12
 
13
+ # @return [Array<Array<String, String>>] list of label value pairs for
14
+ # building time zone select options ordered by offset.
10
15
  def time_zone_select_options
11
16
  filtered_time_zones.collect do |tz|
12
17
  ["(UTC#{tz_utc_offset(tz)}) #{tz_friendly_name(tz)}", tz.name]
@@ -13,6 +13,13 @@
13
13
  <li>
14
14
  <%= link_to 'Reminders', reminders_path %>
15
15
  </li>
16
+
17
+ <% if MrCommon.registration_confirmation_strategy == :pre_register %>
18
+ <li>
19
+ <%= link_to 'PreRegistrations', pre_registrations_path %>
20
+ </li>
21
+ <% end %>
22
+
16
23
  <% if MrCommon.authentication_method_name.present? %>
17
24
  <li>
18
25
  <%= link_to 'Log Out', main_app.send(MrCommon.logout_path_helper), method: MrCommon.logout_path_method %>
@@ -0,0 +1,27 @@
1
+ <div class="row">
2
+ <div class="col">
3
+ <h1>Import Pre Registrations</h1>
4
+ <%= form_with scope: :import, url: pre_registrations_import_index_url, local: true do |f| %>
5
+ <fieldset>
6
+ <legend>Import</legend>
7
+ <div class="form-group">
8
+ <%= f.label :csv_file, "CSV File" %>
9
+ <%= f.file_field :csv_file, required: true, accept: "text/csv" %>
10
+ </div>
11
+ <p>The CSV file should be formatted like this example:</p>
12
+ <pre>
13
+ first_name, last_name, email
14
+ John, Smith, jsmith@example.com
15
+ Jane, Doe, jdoe@example.com
16
+ ,,unknown@example.com
17
+ Name,Only,
18
+ </pre>
19
+ <p>If you have a spreadsheet with these columns, "Save as..." and choose the CSV as the file type.</p>
20
+ <p>It is assumed that your CSV has column headings. The first non-empty row will be ignored.</p>
21
+ </fieldset>
22
+
23
+ <%= f.submit "Import" %>
24
+ <%= link_to "Cancel", pre_registrations_path, class: 'button-secondary' %>
25
+ <% end %>
26
+ </div>
27
+ </div>
@@ -0,0 +1,29 @@
1
+ <%= form_with model: @pre_registration, local: true do |f| %>
2
+ <fieldset>
3
+ <legend>Pre-registration details</legend>
4
+ <%= f.form_group :email do %>
5
+ <%= f.label :email %>
6
+ <%= f.text_field :email %>
7
+ <%= f.errors :email %>
8
+ <% end %>
9
+
10
+ <%= f.form_group :first_name do %>
11
+ <%= f.label :first_name %>
12
+ <%= f.text_field :first_name %>
13
+ <%= f.errors :first_name %>
14
+ <% end %>
15
+
16
+ <%= f.form_group :last_name do %>
17
+ <%= f.label :last_name %>
18
+ <%= f.text_field :last_name %>
19
+ <%= f.errors :last_name %>
20
+ <% end %>
21
+ </fieldset>
22
+
23
+ <%= f.submit "Save" %>
24
+ <% if @pre_registration.new_record? %>
25
+ <%= link_to "Cancel", pre_registrations_path, class: 'button-secondary' %>
26
+ <% else %>
27
+ <%= link_to "Cancel", pre_registration_path(@pre_registration), class: 'button-secondary' %>
28
+ <% end %>
29
+ <% end %>
@@ -0,0 +1,32 @@
1
+ <div class="row">
2
+ <div class="col">
3
+ <h1>PreRegistrations</h1>
4
+ <p>
5
+ <%= link_to 'New', new_pre_registration_path, class: 'button' %>
6
+ </p>
7
+ </div>
8
+ </div>
9
+ <div class="row">
10
+ <div class="col">
11
+ <table>
12
+ <thead>
13
+ <tr>
14
+ <th>Email</th>
15
+ <th>First</th>
16
+ <th>Last</th>
17
+ <th>Actions</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% @pre_registration.each do |r| %>
22
+ <tr>
23
+ <td><%= r.email %></td>
24
+ <td><%= r.first_name %></td>
25
+ <td><%= r.last_name %></td>
26
+ <td><%= link_to 'Delete', r, method: :delete, data: { confirm: 'Are you sure?' } %></td>
27
+ </tr>
28
+ <% end %>
29
+ </tbody>
30
+ </table>
31
+ </div>
32
+ </div>
@@ -1,6 +1,7 @@
1
1
  <div class="row">
2
2
  <div class="col">
3
- <h1>New Registration</h1>
3
+
4
+ <h1>New PreRegistration</h1>
4
5
  <%= render 'form' %>
5
6
  </div>
6
7
  </div>
@@ -1,4 +1,7 @@
1
1
  <h1>Thanks for registering</h1>
2
+
3
+ <p>Your registration is confirmed.</p>
4
+
2
5
  <p>Don't forget to download the reminder and add it to your calendar.</p>
3
6
 
4
7
  <%= render 'mr_common/registration_mailer/html_reminders' %>
@@ -1,6 +1,8 @@
1
1
  Thanks for registering
2
2
  ---
3
3
 
4
+ Your registration is confirmed.
5
+
4
6
  Don't forget to download the reminder and add it to your calendar.
5
7
 
6
8
  <%= render 'mr_common/registration_mailer/text_reminders' %>
@@ -0,0 +1,5 @@
1
+ <h1>Your registration is no longer confirmed</h1>
2
+
3
+ <p>Thank you for your interest in our invitation-only event.</p>
4
+
5
+ <p>Unfortunately, we aren’t able to accommodate your registration at this time. If we are able to confirm a spot for you, we will notify you before the event.</p>
@@ -0,0 +1,6 @@
1
+ Your registration is no longer confirmed
2
+ ---
3
+
4
+ Thank you for your interest in our invitation-only event.
5
+
6
+ Unfortunately, we aren’t able to accommodate your registration at this time. If we are able to confirm a spot for you, we will notify you before the event.