Make open source
This commit is contained in:
27
app/admin/admin_users.rb
Normal file
27
app/admin/admin_users.rb
Normal file
@ -0,0 +1,27 @@
|
||||
ActiveAdmin.register AdminUser do
|
||||
permit_params :email, :password, :password_confirmation
|
||||
|
||||
index do
|
||||
selectable_column
|
||||
id_column
|
||||
column :email
|
||||
column :current_sign_in_at
|
||||
column :sign_in_count
|
||||
column :created_at
|
||||
actions
|
||||
end
|
||||
|
||||
filter :email
|
||||
filter :current_sign_in_at
|
||||
filter :sign_in_count
|
||||
filter :created_at
|
||||
|
||||
form do |f|
|
||||
f.inputs do
|
||||
f.input :email
|
||||
f.input :password
|
||||
f.input :password_confirmation
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
end
|
25
app/admin/attendees.rb
Normal file
25
app/admin/attendees.rb
Normal file
@ -0,0 +1,25 @@
|
||||
ActiveAdmin.register Attendee do
|
||||
scope :child?
|
||||
scope :diet?
|
||||
|
||||
controller do
|
||||
def apply_sorting(chain)
|
||||
params[:order] ? chain : chain.reorder(last_name: :asc, first_name: :asc)
|
||||
end
|
||||
end
|
||||
|
||||
index do
|
||||
column :first_name
|
||||
column :last_name
|
||||
column :email
|
||||
column :diet
|
||||
column :notes
|
||||
column :child
|
||||
column :updated_at
|
||||
end
|
||||
|
||||
config.batch_actions = false
|
||||
config.filters = false
|
||||
config.per_page = 500
|
||||
config.clear_action_items!
|
||||
end
|
32
app/admin/dashboard.rb
Normal file
32
app/admin/dashboard.rb
Normal file
@ -0,0 +1,32 @@
|
||||
ActiveAdmin.register_page 'Dashboard' do
|
||||
menu priority: 1, label: proc { I18n.t('active_admin.dashboard') }
|
||||
|
||||
content title: proc { I18n.t('active_admin.dashboard') } do
|
||||
div class: 'blank_slate_container', id: 'dashboard_default_message' do
|
||||
span class: 'blank_slate' do
|
||||
span I18n.t('active_admin.dashboard_welcome.welcome')
|
||||
small I18n.t('active_admin.dashboard_welcome.call_to_action')
|
||||
end
|
||||
end
|
||||
|
||||
# Here is an example of a simple dashboard with columns and panels.
|
||||
#
|
||||
# columns do
|
||||
# column do
|
||||
# panel "Recent Posts" do
|
||||
# ul do
|
||||
# Post.recent(5).map do |post|
|
||||
# li link_to(post.title, admin_post_path(post))
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# column do
|
||||
# panel "Info" do
|
||||
# para "Welcome to ActiveAdmin."
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
end
|
||||
end
|
21
app/admin/guests.rb
Normal file
21
app/admin/guests.rb
Normal file
@ -0,0 +1,21 @@
|
||||
ActiveAdmin.register Guest do
|
||||
permit_params :email, :first_name, :last_name, :attending, :diet, :songs,
|
||||
:notes
|
||||
|
||||
scope :confirmed
|
||||
scope :attending
|
||||
scope :not_attending
|
||||
|
||||
form do |_f|
|
||||
inputs 'Guest' do
|
||||
input :email, as: :string
|
||||
input :first_name
|
||||
input :last_name
|
||||
input :attending
|
||||
input :diet, as: :text
|
||||
input :songs, as: :text
|
||||
input :notes, as: :text
|
||||
end
|
||||
actions
|
||||
end
|
||||
end
|
13
app/admin/plus_ones.rb
Normal file
13
app/admin/plus_ones.rb
Normal file
@ -0,0 +1,13 @@
|
||||
ActiveAdmin.register PlusOne do
|
||||
permit_params :first_name, :last_name, :diet, :child
|
||||
|
||||
form do |_f|
|
||||
inputs 'Plus One' do
|
||||
input :first_name
|
||||
input :last_name
|
||||
input :diet, as: :text
|
||||
input :child
|
||||
end
|
||||
actions
|
||||
end
|
||||
end
|
3
app/assets/config/manifest.js
Normal file
3
app/assets/config/manifest.js
Normal file
@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
0
app/assets/images/.keep
Normal file
0
app/assets/images/.keep
Normal file
BIN
app/assets/images/flowers.jpg
Normal file
BIN
app/assets/images/flowers.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
app/assets/images/jumbo.jpg
Normal file
BIN
app/assets/images/jumbo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 KiB |
BIN
app/assets/images/plan.jpg
Normal file
BIN
app/assets/images/plan.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
app/assets/images/venue.jpg
Normal file
BIN
app/assets/images/venue.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 296 KiB |
1
app/assets/javascripts/active_admin.js
Normal file
1
app/assets/javascripts/active_admin.js
Normal file
@ -0,0 +1 @@
|
||||
//= require active_admin/base
|
19
app/assets/javascripts/application.js
Normal file
19
app/assets/javascripts/application.js
Normal file
@ -0,0 +1,19 @@
|
||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
|
||||
// vendor/assets/javascripts directory can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
||||
//
|
||||
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
||||
// about supported directives.
|
||||
//
|
||||
//= require rails-ujs
|
||||
//= require jquery
|
||||
//= require popper
|
||||
//= require bootstrap-sprockets
|
||||
|
||||
//= require_tree .
|
||||
//= stub active_admin
|
13
app/assets/javascripts/guests.js
Normal file
13
app/assets/javascripts/guests.js
Normal file
@ -0,0 +1,13 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
||||
|
||||
$(function () {
|
||||
var radioMatcher = '#guest-edit-form input[name="guest[attending]"]'
|
||||
var radioButtons = $(radioMatcher)
|
||||
function toggleAttendingOnly () {
|
||||
var attending = $(radioMatcher + ':checked').val()
|
||||
$('.guest-attending-only textarea').attr('disabled', attending !== 'true')
|
||||
}
|
||||
radioButtons.on('change', toggleAttendingOnly)
|
||||
toggleAttendingOnly()
|
||||
})
|
2
app/assets/javascripts/plus_ones.js
Normal file
2
app/assets/javascripts/plus_ones.js
Normal file
@ -0,0 +1,2 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
2
app/assets/javascripts/welcome.js
Normal file
2
app/assets/javascripts/welcome.js
Normal file
@ -0,0 +1,2 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
65
app/assets/stylesheets/application.scss
Normal file
65
app/assets/stylesheets/application.scss
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
|
||||
* vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url('https://fonts.googleapis.com/css?family=Montserrat:400,700|Arvo:400');
|
||||
|
||||
$font-family-sans-serif: Montserrat, Helvetica, Arial, sans-serif;
|
||||
$font-family-serif: Arvo, Georgia, "Times New Roman", Times, serif;
|
||||
|
||||
$font-family-base: $font-family-serif;
|
||||
$headings-font-family: $font-family-sans-serif;
|
||||
|
||||
$body-color: rgba(28, 13, 10, 0.7);
|
||||
$headings-color: rgba(28, 13, 10, 0.7);
|
||||
|
||||
$theme-colors: (
|
||||
"primary": #ff745c
|
||||
);
|
||||
|
||||
$enable-rounded: false;
|
||||
|
||||
@import "bootstrap";
|
||||
@import "guests";
|
||||
@import "welcome";
|
||||
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.0833333em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: theme-color("primary");
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5em;
|
||||
letter-spacing: 0.0833333em;
|
||||
text-transform: uppercase;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
margin: 0 auto;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.wedding-cta-btn {
|
||||
letter-spacing: 2px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
}
|
||||
|
||||
.wedding-required {
|
||||
color: theme-color("primary");
|
||||
}
|
27
app/assets/stylesheets/guests.scss
Normal file
27
app/assets/stylesheets/guests.scss
Normal file
@ -0,0 +1,27 @@
|
||||
// Place all the styles related to the Guests controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
#guests-leader {
|
||||
background:
|
||||
linear-gradient(rgba(28, 13, 10, 0.4), rgba(28, 13, 10, 0.6)),
|
||||
image-url('flowers.jpg') center center no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
h1 {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.guests-rsvp-nav {
|
||||
padding-bottom: $spacer;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
li.nav-item { width:100%; }
|
||||
}
|
||||
}
|
||||
|
||||
.guests-buttons {
|
||||
padding-top: $spacer;
|
||||
padding-bottom: 10 * $spacer;
|
||||
}
|
3
app/assets/stylesheets/plus_ones.scss
Normal file
3
app/assets/stylesheets/plus_ones.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the PlusOnes controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
104
app/assets/stylesheets/welcome.scss
Normal file
104
app/assets/stylesheets/welcome.scss
Normal file
@ -0,0 +1,104 @@
|
||||
// Place all the styles related to the Welcome controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
#welcome-index-leader {
|
||||
background: linear-gradient(rgba(28, 13, 10, 0.4), rgba(28, 13, 10, 0.6)),
|
||||
image-url("jumbo.jpg") center center no-repeat;
|
||||
background-size: cover;
|
||||
height: 820px;
|
||||
|
||||
* {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
.leader-date {
|
||||
font-size: 17px;
|
||||
letter-spacing: 4px;
|
||||
padding: 12pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-when-where {
|
||||
h1 {
|
||||
padding: 3 * $spacer 0;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-venue-leader {
|
||||
background: linear-gradient(rgba(28, 13, 10, 0.4), rgba(28, 13, 10, 0.6)),
|
||||
image-url("venue.jpg") center center no-repeat;
|
||||
background-size: cover;
|
||||
margin-top: 5 * $spacer;
|
||||
|
||||
* {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 3 * $spacer 0;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-venue {
|
||||
h2 {
|
||||
padding-top: 3 * $spacer;
|
||||
padding-bottom: $spacer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-wedding-plan-leader {
|
||||
background: linear-gradient(rgba(28, 13, 10, 0.4), rgba(28, 13, 10, 0.6)),
|
||||
image-url("plan.jpg") center center no-repeat;
|
||||
background-size: cover;
|
||||
margin-top: 5 * $spacer;
|
||||
|
||||
* {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 3 * $spacer 0;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-wedding-plan {
|
||||
h2 {
|
||||
padding-top: 3 * $spacer;
|
||||
padding-bottom: $spacer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome-index-footer {
|
||||
background-color: #faebe3;
|
||||
margin-top: 5 * $spacer;
|
||||
|
||||
#welcome-index-footer-cta {
|
||||
padding-top: 8 * $spacer;
|
||||
padding-bottom: 3 * $spacer;
|
||||
}
|
||||
|
||||
#welcome-index-footer-credits {
|
||||
padding-top: 5 * $spacer;
|
||||
padding-bottom: 3 * $spacer;
|
||||
|
||||
small {
|
||||
color: rgba(28, 13, 10, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
9
app/controllers/application_controller.rb
Normal file
9
app/controllers/application_controller.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
if ENV['BASIC_AUTH_NAME'] && ENV['BASIC_AUTH_PASSWORD']
|
||||
http_basic_authenticate_with \
|
||||
name: ENV['BASIC_AUTH_NAME'],
|
||||
password: ENV['BASIC_AUTH_PASSWORD']
|
||||
end
|
||||
end
|
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
75
app/controllers/guests_controller.rb
Normal file
75
app/controllers/guests_controller.rb
Normal file
@ -0,0 +1,75 @@
|
||||
class GuestsController < ApplicationController
|
||||
def new
|
||||
respond_to :html
|
||||
|
||||
@guest = Guest.new
|
||||
end
|
||||
|
||||
def create
|
||||
respond_to :html
|
||||
|
||||
@guest = Guest.new(guest_params)
|
||||
unless ENV['RECAPTCHA_SECRET_KEY'].blank? || verify_recaptcha(model: @guest)
|
||||
render :new
|
||||
return
|
||||
end
|
||||
|
||||
if @guest.save
|
||||
redirect_to guest_path(@guest)
|
||||
else
|
||||
existing_guest = Guest.find_by(email: guest_params[:email])
|
||||
if existing_guest
|
||||
@guest = existing_guest
|
||||
GuestMailer.welcome_back_email(@guest).deliver_now
|
||||
render :new_exists
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to :html
|
||||
|
||||
@guest = Guest.find_by_id_token(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@guest = Guest.find_by_id_token(params[:id])
|
||||
|
||||
if @guest.update(guest_params)
|
||||
if @guest.attending?
|
||||
redirect_to guest_plus_ones_path(@guest)
|
||||
else
|
||||
redirect_to confirm_guest_path(@guest)
|
||||
end
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
def confirm
|
||||
respond_to :html
|
||||
|
||||
@guest = Guest.find_by_id_token(params[:id])
|
||||
end
|
||||
|
||||
def complete
|
||||
respond_to :html
|
||||
|
||||
@guest = Guest.find_by_id_token(params[:id])
|
||||
Guest.transaction do
|
||||
@guest.update!(guest_params)
|
||||
@guest.touch :confirmed_at
|
||||
end
|
||||
GuestMailer.confirmation_email(@guest).deliver_now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def guest_params
|
||||
params.require(:guest).permit(
|
||||
:email, :first_name, :last_name, :attending, :diet, :songs, :notes
|
||||
)
|
||||
end
|
||||
end
|
53
app/controllers/plus_ones_controller.rb
Normal file
53
app/controllers/plus_ones_controller.rb
Normal file
@ -0,0 +1,53 @@
|
||||
class PlusOnesController < ApplicationController
|
||||
before_action do
|
||||
@guest = Guest.find_by_id_token(params[:guest_id])
|
||||
end
|
||||
|
||||
def new
|
||||
respond_to :html
|
||||
@plus_one = @guest.plus_ones.new
|
||||
end
|
||||
|
||||
def create
|
||||
respond_to :html
|
||||
@plus_one = @guest.plus_ones.new(plus_one_params)
|
||||
if @plus_one.save
|
||||
redirect_to guest_plus_ones_path(@guest)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to :html
|
||||
@plus_ones = @guest.plus_ones.order(:first_name, :last_name)
|
||||
end
|
||||
|
||||
def edit
|
||||
@plus_one = @guest.plus_ones.find(params[:id])
|
||||
respond_to :html
|
||||
end
|
||||
|
||||
def update
|
||||
@plus_one = @guest.plus_ones.find(params[:id])
|
||||
|
||||
if @plus_one.update(plus_one_params)
|
||||
redirect_to guest_plus_ones_path(@guest)
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
respond_to :html
|
||||
@plus_one = @guest.plus_ones.find_by(id: params[:id])
|
||||
@plus_one.destroy if @plus_one
|
||||
redirect_to guest_plus_ones_path(@guest)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def plus_one_params
|
||||
params.require(:plus_one).permit(:first_name, :last_name, :diet, :child)
|
||||
end
|
||||
end
|
6
app/controllers/welcome_controller.rb
Normal file
6
app/controllers/welcome_controller.rb
Normal file
@ -0,0 +1,6 @@
|
||||
class WelcomeController < ApplicationController
|
||||
def index
|
||||
@google_maps_url = 'https://goo.gl/maps/nBAxNAsmPSS2'
|
||||
@cool_earth_url = 'https://www.coolearth.org/'
|
||||
end
|
||||
end
|
24
app/helpers/application_helper.rb
Normal file
24
app/helpers/application_helper.rb
Normal file
@ -0,0 +1,24 @@
|
||||
module ApplicationHelper
|
||||
def flash_class(key)
|
||||
case key
|
||||
when :notice then 'alert alert-info'
|
||||
when :success then 'alert alert-success'
|
||||
when :error then 'alert alert-error'
|
||||
when :alert then 'alert alert-error'
|
||||
end
|
||||
end
|
||||
|
||||
def errors_for(object)
|
||||
return unless object.errors.any?
|
||||
content_tag(:div, class: 'mb-3 card border-danger') do
|
||||
concat(content_tag(:div, class: 'card-header bg-danger text-white') do
|
||||
concat "Oops, #{pluralize(object.errors.count, 'problem')}:"
|
||||
end)
|
||||
concat(content_tag(:ul, class: 'mb-0 list-group list-group-flush') do
|
||||
object.errors.full_messages.each do |msg|
|
||||
concat content_tag(:li, msg, class: 'list-group-item')
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
2
app/helpers/guests_helper.rb
Normal file
2
app/helpers/guests_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module GuestsHelper
|
||||
end
|
2
app/helpers/plus_ones_helper.rb
Normal file
2
app/helpers/plus_ones_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module PlusOnesHelper
|
||||
end
|
2
app/helpers/welcome_helper.rb
Normal file
2
app/helpers/welcome_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module WelcomeHelper
|
||||
end
|
2
app/jobs/application_job.rb
Normal file
2
app/jobs/application_job.rb
Normal file
@ -0,0 +1,2 @@
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
end
|
4
app/mailers/application_mailer.rb
Normal file
4
app/mailers/application_mailer.rb
Normal file
@ -0,0 +1,4 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'from@example.com'
|
||||
layout 'mailer'
|
||||
end
|
19
app/mailers/guest_mailer.rb
Normal file
19
app/mailers/guest_mailer.rb
Normal file
@ -0,0 +1,19 @@
|
||||
class GuestMailer < ApplicationMailer
|
||||
default from: ENV['FROM_EMAIL'], reply_to: ENV['CONTACT_EMAIL']
|
||||
|
||||
def confirmation_email(guest)
|
||||
@guest = guest
|
||||
mail(
|
||||
to: guest.name_with_email,
|
||||
subject: "#{I18n.t(:wedding_name)}: RSVP Confirmation"
|
||||
)
|
||||
end
|
||||
|
||||
def welcome_back_email(guest)
|
||||
@guest = guest
|
||||
mail(
|
||||
to: guest.name_with_email,
|
||||
subject: "#{I18n.t(:wedding_name)}: Your RSVP"
|
||||
)
|
||||
end
|
||||
end
|
9
app/models/admin_user.rb
Normal file
9
app/models/admin_user.rb
Normal file
@ -0,0 +1,9 @@
|
||||
#
|
||||
# ActiveAdmin console user.
|
||||
#
|
||||
class AdminUser < ApplicationRecord
|
||||
# Include default devise modules. Others available are:
|
||||
# :confirmable, :lockable, :timeoutable and :omniauthable
|
||||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable, :trackable, :validatable
|
||||
end
|
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
7
app/models/attendee.rb
Normal file
7
app/models/attendee.rb
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
# A guest or plus one.
|
||||
#
|
||||
class Attendee < ApplicationRecord
|
||||
scope :diet?, -> { where.not(diet: nil) }
|
||||
scope :child?, -> { where(child: true) }
|
||||
end
|
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
27
app/models/concerns/findable_with_token.rb
Normal file
27
app/models/concerns/findable_with_token.rb
Normal file
@ -0,0 +1,27 @@
|
||||
#
|
||||
# Make a model findable only when a secure token is provided.
|
||||
#
|
||||
module FindableWithToken
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ID_TOKEN_RX = /\A(\d+)-(\w+)\z/
|
||||
|
||||
included do
|
||||
has_secure_token
|
||||
|
||||
def to_param
|
||||
id ? "#{id}-#{token}" : nil
|
||||
end
|
||||
|
||||
def self.find_by_id_token(id_token)
|
||||
raise ActiveRecord::RecordNotFound unless id_token =~ ID_TOKEN_RX
|
||||
id = Regexp.last_match(1)
|
||||
token = Regexp.last_match(2)
|
||||
|
||||
record = find(id)
|
||||
raise ActiveRecord::RecordNotFound unless
|
||||
ActiveSupport::SecurityUtils.secure_compare(record.token, token)
|
||||
record
|
||||
end
|
||||
end
|
||||
end
|
41
app/models/guest.rb
Normal file
41
app/models/guest.rb
Normal file
@ -0,0 +1,41 @@
|
||||
#
|
||||
# A primary guest.
|
||||
#
|
||||
class Guest < ApplicationRecord
|
||||
include FindableWithToken
|
||||
|
||||
auto_strip_attributes :email, :first_name, :last_name, :diet, :songs, :notes
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validates :email, format: Devise.email_regexp, allow_blank: true
|
||||
|
||||
validates :first_name, presence: true, if: :persisted?
|
||||
validates :first_name, length: { maximum: 1024 }
|
||||
validates :last_name, presence: true, if: :persisted?
|
||||
validates :last_name, length: { maximum: 1024 }
|
||||
|
||||
def name
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
def name_with_email
|
||||
"#{name} <#{email}>"
|
||||
end
|
||||
|
||||
# Don't allow long or odd names in emails; may be spam.
|
||||
def email_safe_salutation
|
||||
return 'Hello,' if
|
||||
first_name.blank? || first_name !~ /\A[\p{Word}\s'-]{1,30}\z/i
|
||||
"Dear #{first_name},"
|
||||
end
|
||||
|
||||
validates :diet, length: { maximum: 8192 }
|
||||
validates :songs, length: { maximum: 8192 }
|
||||
validates :notes, length: { maximum: 8192 }
|
||||
|
||||
has_many :plus_ones, dependent: :destroy
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
scope :attending, -> { confirmed.where(attending: true) }
|
||||
scope :not_attending, -> { confirmed.where(attending: false) }
|
||||
end
|
19
app/models/plus_one.rb
Normal file
19
app/models/plus_one.rb
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# An extra guest.
|
||||
#
|
||||
class PlusOne < ApplicationRecord
|
||||
belongs_to :guest
|
||||
|
||||
auto_strip_attributes :first_name, :last_name, :diet
|
||||
|
||||
validates :first_name, presence: true
|
||||
validates :first_name, length: { maximum: 1024 }
|
||||
validates :last_name, presence: true
|
||||
validates :last_name, length: { maximum: 1024 }
|
||||
|
||||
def name
|
||||
"#{first_name} #{last_name}"
|
||||
end
|
||||
|
||||
validates :diet, length: { maximum: 8192 }
|
||||
end
|
3
app/views/application/_mailing_address.html.erb
Normal file
3
app/views/application/_mailing_address.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<strong>Alert</strong><br>
|
||||
Qikiqtaaluk Region, Nunavut<br>
|
||||
Canada<br>
|
3
app/views/application/_wedding_address.html.erb
Normal file
3
app/views/application/_wedding_address.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<strong>Halley Research Station</strong><br>
|
||||
Brunt Ice Shelf<br>
|
||||
Antartica
|
25
app/views/guest_mailer/confirmation_email.html.erb
Normal file
25
app/views/guest_mailer/confirmation_email.html.erb
Normal file
@ -0,0 +1,25 @@
|
||||
<p><%= @guest.email_safe_salutation %></p>
|
||||
|
||||
<% if @guest.attending %>
|
||||
<p>We look forward to seeing you!</p>
|
||||
<p>The address is:</p>
|
||||
<address><%= render partial: 'application/wedding_address' %></address>
|
||||
|
||||
<h3>Your RSVP</h3>
|
||||
<p>If you would like to view or update your RSVP, you can use the following link to get back to it:</p>
|
||||
<p><%= link_to guest_url(@guest), guest_url(@guest) %></p>
|
||||
|
||||
<h3>Sending a Card?</h3>
|
||||
<p>If you need it, our mailing address is:</p>
|
||||
<address><%= render partial: 'application/mailing_address' %></address>
|
||||
|
||||
<h3>Questions and answers</h3>
|
||||
<p>Please check <%= link_to 'our wedding website', root_url %> for more information, or you can reply to this email.</p>
|
||||
<% else %>
|
||||
<p>We're sorry to hear you can't make it. If you change your mind, you can update your RSVP with the link below:</p>
|
||||
<p><%= link_to guest_url(@guest), guest_url(@guest) %></p>
|
||||
<p>We hope to see you another time!</p>
|
||||
<% end %>
|
||||
|
||||
<p> </p>
|
||||
<p><%= t :wedding_couple_names %></p>
|
11
app/views/guest_mailer/welcome_back_email.html.erb
Normal file
11
app/views/guest_mailer/welcome_back_email.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<p><%= @guest.email_safe_salutation %></p>
|
||||
|
||||
<p>You recently entered your email address to RSVP on <%= ENV['CANONICAL_HOST'] %>.</p>
|
||||
|
||||
<p>If you would like to view or update your RSVP, you can use the following link to get back to it:</p>
|
||||
<p><%= link_to guest_url(@guest), guest_url(@guest) %></p>
|
||||
|
||||
<p>If you have any questions, you can ask us by reply.</p>
|
||||
|
||||
<p> </p>
|
||||
<p><%= t :wedding_couple_names %></p>
|
9
app/views/guests/_dietary_preferences.html.erb
Normal file
9
app/views/guests/_dietary_preferences.html.erb
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :diet, 'Dietary Preferences', class: 'col-form-label' %>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.text_area :diet, id: "#{id_stem}_diet", class: 'form-control' %>
|
||||
<small class="form-text text-muted">For example, vegan, vegetarian, gluten free, and any allergies or intolerances.</small>
|
||||
</div>
|
||||
</div>
|
5
app/views/guests/_leader.html.erb
Normal file
5
app/views/guests/_leader.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<div id="guests-leader" class="jumbotron text-center">
|
||||
<div class="container">
|
||||
<h1>RSVP</h1>
|
||||
</div>
|
||||
</div>
|
19
app/views/guests/_name.html.erb
Normal file
19
app/views/guests/_name.html.erb
Normal file
@ -0,0 +1,19 @@
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :first_name, 'First Name', class: 'col-form-label' %>
|
||||
<span class="wedding-required">*</span>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.text_field :first_name, id: "#{id_stem}_first_name", class: 'form-control', autofocus: true, required: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :last_name, 'Last Name', class: 'col-form-label' %>
|
||||
<span class="wedding-required">*</span>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.text_field :last_name, id: "#{id_stem}_last_name", class: 'form-control', required: true %>
|
||||
</div>
|
||||
</div>
|
21
app/views/guests/_rsvp_nav.html.erb
Normal file
21
app/views/guests/_rsvp_nav.html.erb
Normal file
@ -0,0 +1,21 @@
|
||||
<nav class="guests-rsvp-nav">
|
||||
<ul class="nav nav-pills nav-fill">
|
||||
<li class="nav-item">
|
||||
<%= link_to '1. About You', guest_path(@guest), class: ['nav-link', active == 1 ? 'active' : ''] %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<% if active >= 2 %>
|
||||
<%= link_to '2. Plus Ones', guest_plus_ones_path(@guest), class: ['nav-link', active == 2 ? 'active' : ''] %>
|
||||
<% else %>
|
||||
<a class="nav-link <%= active == 2 ? 'active' : '' %>">2. Plus Ones</a>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<% if active >= 3 %>
|
||||
<%= link_to '3. Confirm', confirm_guest_path(@guest), class: ['nav-link', active == 3 ? 'active' : ''] %>
|
||||
<% else %>
|
||||
<a class="nav-link <%= active == 3 ? 'active' : '' %>">3. Confirm</a>
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
12
app/views/guests/complete.html.erb
Normal file
12
app/views/guests/complete.html.erb
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'leader' %>
|
||||
<h2>Thanks!</h2>
|
||||
<p>We've sent an email confirmation to <tt><%= @guest.email %></tt>.</p>
|
||||
<p>If you don't receive the email within a few minutes, please check your spam folder, and if it's not there <%= mail_to ENV['CONTACT_EMAIL'], 'contact us' %>.</p>
|
||||
<p>The email contains a link that you can use to update your RSVP later if you need to.</p>
|
||||
<p><%= link_to 'Back to the Home Page', root_path %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
98
app/views/guests/confirm.html.erb
Normal file
98
app/views/guests/confirm.html.erb
Normal file
@ -0,0 +1,98 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'leader' %>
|
||||
<%= render partial: 'rsvp_nav', locals: { active: 3 } %>
|
||||
|
||||
<h2>Confirm</h2>
|
||||
|
||||
<%= form_with(model: @guest, url: complete_guest_path(@guest), local: true) do |form| %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<%= errors_for(@guest) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @guest.attending? %>
|
||||
<h3>Your Details</h3>
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header"><%= @guest.name %></h5>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Email
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<p><tt><%= @guest.email %></tt></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Dietary Preferences
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<% if @guest.diet.present? %>
|
||||
<pre><%= @guest.diet %></pre>
|
||||
<% else %>
|
||||
<p>(None)</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Song Suggestions
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<% if @guest.songs.present? %>
|
||||
<pre><%= @guest.songs %></pre>
|
||||
<% else %>
|
||||
<p>(None)</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<%= link_to 'Edit', guest_path(@guest), class: 'card-link', data: { disable_with: 'Loading...' } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Plus Ones</h3>
|
||||
<% if @guest.plus_ones.any? %>
|
||||
<%= render @guest.plus_ones %>
|
||||
<% else %>
|
||||
<p>(None — just you.)</p>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p>Sorry to hear you won't be joining us!</p>
|
||||
<h3>Your Details</h3>
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header"><%= @guest.name %></h5>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Email
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<p><tt><%= @guest.email %></tt></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h3>Other Comments?</h3>
|
||||
|
||||
<%= form.text_area :notes, class: 'form-control' %>
|
||||
<small class="form-text text-muted">Anything else you'd like to let us know?</small>
|
||||
|
||||
<div class="row guests-buttons">
|
||||
<div class="offset-md-3 col-md-6 text-center">
|
||||
<%= form.submit 'Complete RSVP', class: 'btn btn-primary', data: { disable_with: 'Sending...' } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
30
app/views/guests/new.html.erb
Normal file
30
app/views/guests/new.html.erb
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'leader' %>
|
||||
<p>Please enter your email address to begin. We'll send you an email with important information once you have RSVP'd.</p>
|
||||
<%= form_with scope: :guest, url: guests_path, local: true do |form| %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<%= errors_for(@guest) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :email, class: 'col-form-label' %>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.email_field :email, id: 'guest_email', class: 'form-control', autofocus: true, required: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row guests-buttons">
|
||||
<div class="offset-md-3 col-md-6 text-center">
|
||||
<%= invisible_recaptcha_tags text: 'Continue', class: 'btn btn-primary', data: { disable_with: 'Sending...' } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
app/views/guests/new_exists.html.erb
Normal file
11
app/views/guests/new_exists.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'leader' %>
|
||||
<h2>Welcome Back</h2>
|
||||
<p>It looks like you've already registered your RSVP, so we've sent you an email with a link that you can use to update it.</p>
|
||||
<p>Please check your <tt><%= @guest.email %></tt> email including spam folders!</p>
|
||||
<p>If you don't receive the email, please <%= mail_to ENV['CONTACT_EMAIL'], 'contact us' %>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
67
app/views/guests/show.html.erb
Normal file
67
app/views/guests/show.html.erb
Normal file
@ -0,0 +1,67 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'leader' %>
|
||||
<%= render partial: 'rsvp_nav', locals: { active: 1 } %>
|
||||
|
||||
<h2>About You</h2>
|
||||
|
||||
<%= form_with(model: @guest, method: :patch, local: true, id: 'guest-edit-form') do |form| %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<%= errors_for(@guest) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :email, class: 'col-form-label' %>
|
||||
<span class="wedding-required">*</span>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.email_field :email, id: 'guest_email', class: 'form-control', required: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'name', locals: { form: form, id_stem: 'guest' } %>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="offset-md-3 col-md-9">
|
||||
<p><strong>Will you be joining us?</strong></p>
|
||||
<div class="form-check form-check-inline">
|
||||
<%= form.radio_button :attending, 'true', checked: true, id: 'guest_attending_true', class: 'form-check-input' %>
|
||||
<%= form.label :attending, 'Yes', value: 'true', class: 'form-check-label' %>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<%= form.radio_button :attending, 'false', id: 'guest_attending_false', class: 'form-check-input' %>
|
||||
<%= form.label :attending, 'No', value: 'false', class: 'form-check-label' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guest-attending-only">
|
||||
<%= render partial: 'dietary_preferences', locals: { form: form, id_stem: 'guest' } %>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<%= form.label :songs, 'Song Suggestions', class: 'col-form-label' %>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<%= form.text_area :songs, id: 'guest_songs', class: 'form-control' %>
|
||||
<small class="form-text text-muted">We're crowd sourcing our playlist! Choose some songs you'd like to hear.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row guests-buttons">
|
||||
<div class="col-md-3">
|
||||
<span class="wedding-required">* Required</span>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<%= form.submit 'Continue', class: 'btn btn-primary', data: { disable_with: 'Sending...' } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
21
app/views/layouts/application.html.erb
Normal file
21
app/views/layouts/application.html.erb
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= t :wedding_name %></title>
|
||||
<%= csrf_meta_tags %>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all' %>
|
||||
<%= javascript_include_tag 'application' %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<% flash.each do |key, value| %>
|
||||
<%= content_tag(:div, class: flash_class(key)) do -%>
|
||||
<%= value %>
|
||||
<% end -%>
|
||||
<% end %>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
10
app/views/layouts/mailer.html.erb
Normal file
10
app/views/layouts/mailer.html.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
1
app/views/layouts/mailer.text.erb
Normal file
1
app/views/layouts/mailer.text.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= yield %>
|
32
app/views/plus_ones/_form.html.erb
Normal file
32
app/views/plus_ones/_form.html.erb
Normal file
@ -0,0 +1,32 @@
|
||||
<%= form_with(model: [@guest, @plus_one], local: true) do |form| %>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<%= errors_for(@plus_one) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'guests/name', locals: { form: form, id_stem: 'plus_one' } %>
|
||||
|
||||
<%= render partial: 'guests/dietary_preferences', locals: { form: form, id_stem: 'plus_one' } %>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="offset-md-3 col-md-9">
|
||||
<div class="form-check form-check-inline">
|
||||
<%= form.check_box :child, id: 'plus_one_child', class: 'form-check-input' %>
|
||||
<%= form.label :child, 'Child (12 or under)', class: 'form-check-label' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row guests-buttons">
|
||||
<div class="col-md-3">
|
||||
<span class="wedding-required">* Required</span>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<%= form.submit submit_action, class: 'btn btn-primary', data: { disable_with: 'Sending...' } %>
|
||||
</div>
|
||||
<div class="col-md-3 text-right">
|
||||
<%= link_to 'Cancel', guest_plus_ones_path(@guest), class: 'btn btn-secondary' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
31
app/views/plus_ones/_plus_one.html.erb
Normal file
31
app/views/plus_ones/_plus_one.html.erb
Normal file
@ -0,0 +1,31 @@
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header"><%= plus_one.name %></h5>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Dietary Preferences
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<% if plus_one.diet.present? %>
|
||||
<pre><%= plus_one.diet %></pre>
|
||||
<% else %>
|
||||
<p>(None)</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
Child
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<p><%= plus_one.child ? 'Yes' : 'No' %></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<%= link_to 'Edit', edit_guest_plus_one_path(plus_one.guest, plus_one), class: 'card-link', data: { disable_with: 'Loading...' } %>
|
||||
<%= link_to 'Remove', guest_plus_one_path(plus_one.guest, plus_one), method: :delete, class: 'card-link', data: { disable_with: 'Removing...', confirm: 'Are you sure?' } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
app/views/plus_ones/edit.html.erb
Normal file
11
app/views/plus_ones/edit.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'guests/leader' %>
|
||||
<%= render partial: 'guests/rsvp_nav', locals: { active: 2 } %>
|
||||
|
||||
<h2>Edit Plus One</h2>
|
||||
<%= render partial: 'form', locals: { submit_action: 'Save Plus One' } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
26
app/views/plus_ones/index.html.erb
Normal file
26
app/views/plus_ones/index.html.erb
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'guests/leader' %>
|
||||
<%= render partial: 'guests/rsvp_nav', locals: { active: 2 } %>
|
||||
|
||||
<h2>Plus Ones</h2>
|
||||
<p>Please let us know below if you're bringing someone else to the wedding. Children are also very welcome.
|
||||
<p> </p>
|
||||
|
||||
<% if @plus_ones.any? %>
|
||||
<%= render @plus_ones %>
|
||||
<% else %>
|
||||
<p>No plus ones added yet. Flying solo.</p>
|
||||
<% end %>
|
||||
<p>
|
||||
<%= link_to 'Add Plus One', new_guest_plus_one_path(@guest), class: 'btn btn-primary', data: { disable_with: 'Loading...' } %>
|
||||
</p>
|
||||
|
||||
<p> </p>
|
||||
<p class="guests-buttons">
|
||||
<%= link_to 'Continue', confirm_guest_path(@guest), class: 'btn btn-primary' %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
11
app/views/plus_ones/new.html.erb
Normal file
11
app/views/plus_ones/new.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-lg-2 col-lg-8">
|
||||
<%= render partial: 'guests/leader' %>
|
||||
<%= render partial: 'guests/rsvp_nav', locals: { active: 2 } %>
|
||||
|
||||
<h2>Add Plus One</h2>
|
||||
<%= render partial: 'form', locals: { submit_action: 'Add Plus One' } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
3
app/views/welcome/_calendar_svg.html.erb
Normal file
3
app/views/welcome/_calendar_svg.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 8 8">
|
||||
<path d="M0 0v2h7v-2h-7zm0 3v4.91c0 .05.04.09.09.09h6.81c.05 0 .09-.04.09-.09v-4.91h-7zm1 1h1v1h-1v-1zm2 0h1v1h-1v-1zm2 0h1v1h-1v-1zm-4 2h1v1h-1v-1zm2 0h1v1h-1v-1z" />
|
||||
</svg>
|
After Width: | Height: | Size: 263 B |
14
app/views/welcome/_footer.html.erb
Normal file
14
app/views/welcome/_footer.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<footer id="welcome-index-footer" class="page-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div id="welcome-index-footer-cta" class="col-md-12 text-center">
|
||||
<%= link_to 'RSVP TODAY!', new_guest_path, class: 'btn btn-primary wedding-cta-btn' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center" id="welcome-index-footer-credits">
|
||||
<small><% t :photo_credits %></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
11
app/views/welcome/_jumbo.html.erb
Normal file
11
app/views/welcome/_jumbo.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<section id="welcome-index-leader" class="jumbotron text-center">
|
||||
<div class="container">
|
||||
<h1 class="jumbotron-heading"><%= t :wedding_couple_names %></h1>
|
||||
<p class="leader-date">
|
||||
<%= t :wedding_date %>, <%= t :wedding_location %>
|
||||
</p>
|
||||
<p>
|
||||
<%= link_to 'RSVP TODAY!', new_guest_path, class: 'btn btn-primary wedding-cta-btn' %>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
3
app/views/welcome/_map_marker_svg.html.erb
Normal file
3
app/views/welcome/_map_marker_svg.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 8 8">
|
||||
<path d="M3 0c-1.66 0-3 1.34-3 3 0 2 3 5 3 5s3-3 3-5c0-1.66-1.34-3-3-3zm0 1c1.11 0 2 .9 2 2 0 1.11-.89 2-2 2-1.1 0-2-.89-2-2 0-1.1.9-2 2-2z" transform="translate(1)" />
|
||||
</svg>
|
After Width: | Height: | Size: 264 B |
40
app/views/welcome/_plan.html.erb
Normal file
40
app/views/welcome/_plan.html.erb
Normal file
@ -0,0 +1,40 @@
|
||||
<section id="welcome-index-wedding-plan-leader" class="jumbotron text-center">
|
||||
<div class="container">
|
||||
<h1>The Plan</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section id="welcome-index-wedding-plan">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-md-3 col-md-6">
|
||||
<h2>Order of the Day</h2>
|
||||
<p><strong>12:30pm</strong> — Guests arrive</p>
|
||||
<p><strong>1pm</strong> — Wedding service</p>
|
||||
<p><strong>1:30pm</strong> — Cocktails, photos, confetti and canapes</p>
|
||||
<p><strong>3pm-6pm</strong> — Food and drink</p>
|
||||
<p><strong>7pm</strong> — Cutting of the cake! Drinking, dancing!</p>
|
||||
<p><strong>12am</strong> — Bar closes</p>
|
||||
<p>There will be a tab for the evening, followed by a cash bar.</p>
|
||||
<p><strong>Dress code</strong> is warm.</p>
|
||||
<h2>Gifts & Cards</h2>
|
||||
<p>We'd be pleased if you made a donation to charity rather than bringing a gift. Our goal is to raise £1,000 for <%= link_to 'Cool Earth', @cool_earth_url %>, the non-profit that works alongside rainforest communities to halt deforestation and climate change.</p>
|
||||
<p class="text-center"><%= link_to 'Donate to Cool Earth', @cool_earth_url, class: 'btn btn-primary wedding-cta-btn' %></p>
|
||||
<p>If you'd like to send us a card, we'll send you our mailing address by email once you RSVP.</p>
|
||||
<h2>Menu</h2>
|
||||
<p><strong>Starter</strong> — Home Cured Salmon Gravadlax with Horseradish Cream & Beetroot Tart <sup>*</sup> †</p>
|
||||
<p><strong>Main</strong> — Fricassee of Chicken with Lyonnaise Potato ‡</p>
|
||||
<p><strong>Dessert</strong> — Choux Swan with Chocolate & Brandy Sauce <sup>§</sup></p>
|
||||
<p><strong>Children's Menu</strong>: Garlic bread, chicken goujons and chips, ice cream.</p>
|
||||
<ul class="list-unstyled">
|
||||
<li><small><sup>*</sup> Gluten free & vegan starter: Tomato soup</small></li>
|
||||
<li><small>† Gluten free & dairy free starter: Gravadlax served without tart</small></li>
|
||||
<li><small>‡ Main is gluten free. Vegan main: Aubergine & Black Bean Chilli with Cumin Rice</small></li>
|
||||
<li><small><sup>§</sup> Vegan, gluten free & dairy free dessert: Fruit platter</small></li>
|
||||
</ul>
|
||||
<p>The venue is making every effort to cater for vegan, vegetarian, gluten free, allergies and other dietary preferences, as indicated in your RSVPs.</p>
|
||||
<h2>RSVPs</h2>
|
||||
<p>Please RSVP as soon as possible, so we know who is coming and can make sure we keep a seat and a piece of cake for you if you can come! If it turns out you can’t make it, please let us know as soon as you can.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
19
app/views/welcome/_venue.html.erb
Normal file
19
app/views/welcome/_venue.html.erb
Normal file
@ -0,0 +1,19 @@
|
||||
<section id="welcome-index-venue-leader" class="jumbotron text-center">
|
||||
<div class="container">
|
||||
<h1>The Venue</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section id="welcome-index-venue">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="offset-md-3 col-md-6">
|
||||
<h2>How do I get there?</h2>
|
||||
<p>Ships sail regularly from Ushuaia, Argentina. Allow two days.</p>
|
||||
<p>There is ample snow mobile parking. Watch out for penguins.</p>
|
||||
<h2>Coming from out of town?</h2>
|
||||
<p>The station has a number of rooms that you can get at the <strong>discounted wedding rate</strong>.</p>
|
||||
<p>Check in is from 3pm, so you may not be able to get ready in your room if you book it just for the night of the wedding.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
33
app/views/welcome/_when_where.html.erb
Normal file
33
app/views/welcome/_when_where.html.erb
Normal file
@ -0,0 +1,33 @@
|
||||
<section id="welcome-index-when-where">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<h1>When & Where</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-center">
|
||||
<h2><%= link_to 'wedding.ics' do -%>
|
||||
<%= render partial: 'calendar_svg' %>
|
||||
<%- end %></h2>
|
||||
<p>
|
||||
<strong>12:30pm – late</strong><br>
|
||||
<%= t :wedding_date %><br>
|
||||
<br>
|
||||
</p>
|
||||
<p><%= link_to 'Add to Calendar', 'wedding.ics' %></p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center">
|
||||
<h2><%= link_to @google_maps_url do -%>
|
||||
<%= render partial: 'map_marker_svg' %>
|
||||
<%- end %></h2>
|
||||
<p>
|
||||
<%= render partial: 'wedding_address' %>
|
||||
</p>
|
||||
<p>
|
||||
<%= link_to 'Google Maps', @google_maps_url %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
7
app/views/welcome/index.html.erb
Normal file
7
app/views/welcome/index.html.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<main role="main">
|
||||
<%= render partial: 'jumbo' %>
|
||||
<%= render partial: 'when_where' %>
|
||||
<%= render partial: 'venue' %>
|
||||
<%= render partial: 'plan' %>
|
||||
</main>
|
||||
<%= render partial: 'footer' %>
|
Reference in New Issue
Block a user