diff --git a/.env.template b/.env.template index d319d37a..4d7ce020 100644 --- a/.env.template +++ b/.env.template @@ -6,3 +6,4 @@ RSPEC_RETRY_RETRY_COUNT=0 MAILER_SENDER="Rails World " REGISTRATION_ENABLED=true SESSION_REMINDERS_ENABLED=true +ONLY_TESTER_REGISTRATION_ENABLED=false \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index a054b2a8..3b3b1f4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -625,6 +625,7 @@ PLATFORMS arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES diff --git a/app/assets/images/icons/bookmark-full.svg b/app/assets/images/icons/bookmark-full.svg index b44b0b8e..616048f0 100644 --- a/app/assets/images/icons/bookmark-full.svg +++ b/app/assets/images/icons/bookmark-full.svg @@ -1,3 +1,3 @@ - + diff --git a/app/assets/images/icons/calendar.png b/app/assets/images/icons/calendar.png new file mode 100644 index 00000000..0a8b8b08 Binary files /dev/null and b/app/assets/images/icons/calendar.png differ diff --git a/app/assets/images/icons/chevron_left.svg b/app/assets/images/icons/chevron_left.svg new file mode 100644 index 00000000..c4f4fa01 --- /dev/null +++ b/app/assets/images/icons/chevron_left.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/assets/images/icons/eye-empty.svg b/app/assets/images/icons/eye-empty.svg new file mode 100644 index 00000000..0eef4117 --- /dev/null +++ b/app/assets/images/icons/eye-empty.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/images/icons/eye-off.svg b/app/assets/images/icons/eye-off.svg new file mode 100644 index 00000000..617fedc7 --- /dev/null +++ b/app/assets/images/icons/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/assets/images/icons/location.png b/app/assets/images/icons/location.png new file mode 100644 index 00000000..2445808f Binary files /dev/null and b/app/assets/images/icons/location.png differ diff --git a/app/assets/images/icons/no_wifi.svg b/app/assets/images/icons/no_wifi.svg new file mode 100644 index 00000000..84ebebef --- /dev/null +++ b/app/assets/images/icons/no_wifi.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/assets/images/intersection_lines.svg b/app/assets/images/intersection_lines.svg new file mode 100644 index 00000000..470866d6 --- /dev/null +++ b/app/assets/images/intersection_lines.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/assets/images/main_logo.png b/app/assets/images/main_logo.png new file mode 100644 index 00000000..833b1b3a Binary files /dev/null and b/app/assets/images/main_logo.png differ diff --git a/app/assets/images/splashscreens/iPhone_12_Mini.png b/app/assets/images/splashscreens/iPhone_12_Mini.png new file mode 100644 index 00000000..00308396 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_12_Mini.png differ diff --git a/app/assets/images/splashscreens/iPhone_12_Pro.png b/app/assets/images/splashscreens/iPhone_12_Pro.png new file mode 100644 index 00000000..f03e8877 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_12_Pro.png differ diff --git a/app/assets/images/splashscreens/iPhone_12_Pro_Max.png b/app/assets/images/splashscreens/iPhone_12_Pro_Max.png new file mode 100644 index 00000000..83e01661 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_12_Pro_Max.png differ diff --git a/app/assets/images/splashscreens/iPhone_13_Mini.png b/app/assets/images/splashscreens/iPhone_13_Mini.png new file mode 100644 index 00000000..00308396 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_13_Mini.png differ diff --git a/app/assets/images/splashscreens/iPhone_13_Pro.png b/app/assets/images/splashscreens/iPhone_13_Pro.png new file mode 100644 index 00000000..f03e8877 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_13_Pro.png differ diff --git a/app/assets/images/splashscreens/iPhone_13_Pro_Max.png b/app/assets/images/splashscreens/iPhone_13_Pro_Max.png new file mode 100644 index 00000000..83e01661 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_13_Pro_Max.png differ diff --git a/app/assets/images/splashscreens/iPhone_14.png b/app/assets/images/splashscreens/iPhone_14.png new file mode 100644 index 00000000..f03e8877 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_14.png differ diff --git a/app/assets/images/splashscreens/iPhone_14_Plus.png b/app/assets/images/splashscreens/iPhone_14_Plus.png new file mode 100644 index 00000000..83e01661 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_14_Plus.png differ diff --git a/app/assets/images/splashscreens/iPhone_14_Pro.png b/app/assets/images/splashscreens/iPhone_14_Pro.png new file mode 100644 index 00000000..f03e8877 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_14_Pro.png differ diff --git a/app/assets/images/splashscreens/iPhone_14_Pro_Max.png b/app/assets/images/splashscreens/iPhone_14_Pro_Max.png new file mode 100644 index 00000000..0b97eab3 Binary files /dev/null and b/app/assets/images/splashscreens/iPhone_14_Pro_Max.png differ diff --git a/app/assets/images/telos_labs_logo.svg b/app/assets/images/telos_labs_logo.svg index 462c8125..69ff7473 100644 --- a/app/assets/images/telos_labs_logo.svg +++ b/app/assets/images/telos_labs_logo.svg @@ -1,19 +1,12 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css index 3cfcb2b7..1df77bb3 100644 --- a/app/assets/stylesheets/actiontext.css +++ b/app/assets/stylesheets/actiontext.css @@ -29,3 +29,9 @@ padding: 0 !important; max-width: 100% !important; } + +.trix-content a { + color: #CB0C1C; + font-weight: 400; + text-decoration: underline; +} diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index e6ae6df2..e0e13275 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -1,9 +1,9 @@ +@import 'actiontext.css'; + @tailwind base; @tailwind components; @tailwind utilities; -@import 'actiontext.css'; - @layer base { ul, ol { @apply block ml-4 @@ -58,3 +58,40 @@ div.field_with_errors > input { @apply px-0; } } + +/* Utility classes for iOS PWA support */ +.pt-safe-area-6 { + padding-top: calc(env(safe-area-inset-top) + 24px) +} + +.pt-safe-area-2 { + padding-top: calc(env(safe-area-inset-top) + 8px) +} + +.pb-safe-area-1 { + padding-bottom: calc(env(safe-area-inset-bottom) + 4px); +} + +.pt-safe-area-back-button { + padding-top: calc(env(safe-area-inset-top) + 40px); +} + +.pt-safe-area-header { + padding-top: calc(env(safe-area-inset-top) + 79px); +} + +.pb-safe-area-bottom-navbar { + padding-bottom: calc(env(safe-area-inset-bottom) + 54px); +} + +.top-safe-area-header { + top: calc(env(safe-area-inset-top) + 79px); +} + +.top-safe-area-with-filters { + top: calc(env(safe-area-inset-top) + 245px); +} + +.top-safe-area-no-filters { + top: calc(env(safe-area-inset-top) + 212px); +} diff --git a/app/avo/resources/conference.rb b/app/avo/resources/conference.rb index cb269045..bc918edd 100644 --- a/app/avo/resources/conference.rb +++ b/app/avo/resources/conference.rb @@ -10,7 +10,7 @@ class Avo::Resources::Conference < Avo::BaseResource def fields field :id, as: :id - field :name, as: :text + field :name, as: :text, link_to_record: true field :locations, as: :has_many field :sessions, as: :has_many end diff --git a/app/avo/resources/location.rb b/app/avo/resources/location.rb index 45c94222..9d9ed072 100644 --- a/app/avo/resources/location.rb +++ b/app/avo/resources/location.rb @@ -10,7 +10,7 @@ class Avo::Resources::Location < Avo::BaseResource def fields field :id, as: :id - field :name, as: :text, sortable: true + field :name, as: :text, sortable: true, link_to_record: true field :conference, as: :belongs_to field :sessions, as: :has_many end diff --git a/app/avo/resources/session.rb b/app/avo/resources/session.rb index 7256ea45..2f092693 100644 --- a/app/avo/resources/session.rb +++ b/app/avo/resources/session.rb @@ -18,9 +18,10 @@ class Avo::Resources::Session < Avo::BaseResource def fields field :id, as: :id - field :title, as: :text, sortable: true + field :title, as: :text, sortable: true, link_to_record: true field :slug, as: :text, hide_on: :new field :description, as: :trix + field :public, as: :boolean field :starts_at, as: :date_time, help: "The datetime field will use your browser's current timezone.", sortable: true, format: "FFFF" diff --git a/app/avo/resources/speaker.rb b/app/avo/resources/speaker.rb index 494f7964..2c0fb152 100644 --- a/app/avo/resources/speaker.rb +++ b/app/avo/resources/speaker.rb @@ -18,8 +18,8 @@ class Avo::Resources::Speaker < Avo::BaseResource def fields field :id, as: :id + field :name, as: :text, sortable: -> { query.order("profiles.name #{direction}") }, link_to_record: true field :slug, as: :text, hide_on: :new - field :name, as: :text, sortable: -> { query.order("profiles.name #{direction}") } field :image, as: :file, accept: "image/*", only_on: [:show, :forms] field :image_presence, name: "Image", as: :boolean, only_on: :index do record.image.attached? diff --git a/app/avo/resources/tag.rb b/app/avo/resources/tag.rb index e58acdf0..7c339426 100644 --- a/app/avo/resources/tag.rb +++ b/app/avo/resources/tag.rb @@ -10,7 +10,7 @@ class Avo::Resources::Tag < Avo::BaseResource def fields field :id, as: :id - field :name, as: :text, sortable: true + field :name, as: :text, sortable: true, link_to_record: true field :sessions, as: :has_and_belongs_to_many end end diff --git a/app/avo/resources/user.rb b/app/avo/resources/user.rb index 90be14c5..ebf849d2 100644 --- a/app/avo/resources/user.rb +++ b/app/avo/resources/user.rb @@ -13,7 +13,7 @@ class Avo::Resources::User < Avo::BaseResource def fields field :id, as: :id - field :email, as: :text, sortable: true + field :email, as: :text, sortable: true, link_to_record: true field :role, as: :select, options: User.roles, include_blank: true, sortable: true, default: "user" field :password, as: :password, only_on: :new field :password_confirmation, as: :password, only_on: :new diff --git a/app/constraints/authenticated_constraint.rb b/app/constraints/authenticated_constraint.rb new file mode 100644 index 00000000..9bd45ece --- /dev/null +++ b/app/constraints/authenticated_constraint.rb @@ -0,0 +1,5 @@ +class AuthenticatedConstraint + def matches?(request) + request.session[:user_id].present? + end +end diff --git a/app/constraints/unauthenticated_constraint.rb b/app/constraints/unauthenticated_constraint.rb new file mode 100644 index 00000000..7c8b3116 --- /dev/null +++ b/app/constraints/unauthenticated_constraint.rb @@ -0,0 +1,5 @@ +class UnauthenticatedConstraint + def matches?(request) + request.session[:user_id].nil? + end +end diff --git a/app/controllers/abouts_controller.rb b/app/controllers/abouts_controller.rb index e17cc4de..dee0a886 100644 --- a/app/controllers/abouts_controller.rb +++ b/app/controllers/abouts_controller.rb @@ -1,4 +1,5 @@ class AboutsController < ApplicationController + allow_unauthenticated_access def show end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2c2fbf3c..3d802219 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,14 +7,24 @@ class ApplicationController < ActionController::Base helper_method :current_profile, :current_conference, :vapid_public_key + rescue_from ActionController::InvalidAuthenticityToken, + with: :after_invalid_authenticity_token + private def current_profile = current_user&.profile - # TODO: Must change after implementing multi-conference support - def current_conference = Conference.last + def current_conference + @_current_conference ||= Conference.last + end def vapid_public_key - Base64.urlsafe_decode64(ENV["VAPID_PUBLIC_KEY"]).bytes.to_json + @_vapid_public_key ||= Base64.urlsafe_decode64(ENV["VAPID_PUBLIC_KEY"]).bytes.to_json + end + + def after_invalid_authenticity_token + path_to_redirect = request.referer + path_to_redirect ||= user_signed_in? ? sessions_path : new_user_session_path + redirect_to path_to_redirect, alert: t("authorization.invalid_auth_token") end end diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index 0fa41334..da010ffa 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -27,6 +27,7 @@ def authenticate_user! authenticate_user if !user_signed_in? + flash[:notice] = I18n.t("authentication.unauthenticated") redirect_to new_user_session_path end end diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 67aa3c74..d6017bfa 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -3,12 +3,12 @@ def show @sessions = SessionQuery.new( relation: current_user.sessions.where(conference: current_conference), params: filter_params - ).call.includes(:attendees, :location, :speakers, :tags).order(:starts_at) + ).call.includes(:location, :tags, speakers: [profile: :image_attachment]).order(:starts_at) end private def filter_params - params.permit(:starts_at, :live, :past, :starting_soon) + params.permit(:starts_at, :live, :past, :starting_soon).merge(show_private: user_signed_in?) end end diff --git a/app/controllers/service_worker_controller.rb b/app/controllers/service_worker_controller.rb index a095928b..f1ddc25a 100644 --- a/app/controllers/service_worker_controller.rb +++ b/app/controllers/service_worker_controller.rb @@ -8,4 +8,7 @@ def service_worker def manifest end + + def offline + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 40ce9583..485a0395 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,23 +1,30 @@ class SessionsController < ApplicationController + allow_unauthenticated_access + def index - @user_session_ids = current_user.sessions.pluck(:id) + @user_session_ids = current_user&.sessions&.pluck(:id) @sessions = SessionQuery.new( relation: sessions.joins(:location).distinct, params: filter_params - ).call.includes(:attendees, :tags).order(:starts_at) + ).call.includes(:location, :tags, speakers: [profile: :image_attachment]).order(:starts_at) end def show - @session = sessions.friendly.find(params[:id]) + @user_session_ids = current_user&.sessions&.pluck(:id) || [] + @session = if user_signed_in? + sessions.friendly.includes(:location, :tags, speakers: [profile: :image_attachment]).find(params[:id]) + else + sessions.publics.friendly.includes(:location, :tags, speakers: [profile: :image_attachment]).find(params[:id]) + end end private def sessions - current_conference&.sessions + Session.where(conference: current_conference) end def filter_params - params.permit(:starts_at, :live, :past, :starting_soon) + params.permit(:starts_at, :live, :past, :starting_soon).merge(show_private: user_signed_in?) end end diff --git a/app/controllers/speakers_controller.rb b/app/controllers/speakers_controller.rb index 7bb1a30c..0f1c0747 100644 --- a/app/controllers/speakers_controller.rb +++ b/app/controllers/speakers_controller.rb @@ -1,4 +1,5 @@ class SpeakersController < ApplicationController + allow_unauthenticated_access only: [:show] def show @speaker = current_conference.speakers.friendly.find(params[:id]) @profile = @speaker.profile.presence || Profile.new diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 00000000..de292142 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,39 @@ +class UsersController < ApplicationController + def edit + end + + def update + if Current.user.update(user_params) + redirect_to edit_user_path, notice: t("controllers.users.update.success") + else + render :edit, status: :unprocessable_entity + end + end + + private + + def permitted_params + params.require(:user).permit( + :email, + :password, + :password_confirmation, + :password_challenge + ).with_defaults(password_challenge: "") + end + + def user_params + if password_params_blank? + {email: permitted_params[:email]} + else + permitted_params + end + end + + def password_params_blank? + permitted_params.values_at( + :password, + :password_confirmation, + :password_challenge + ).all?(&:blank?) + end +end diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb index 0273d63f..9be84cbd 100644 --- a/app/helpers/navigation_helper.rb +++ b/app/helpers/navigation_helper.rb @@ -1,31 +1,71 @@ module NavigationHelper - def nav_icon_class_for(path) - return "fill-red w-6 h-6" if path.any? { |p| current_page?(p) } + def nav_icon_class_for(paths) + return "fill-red w-6 h-6" if paths.any? { |p| active_path?(p) } "fill-grey-400 w-6 h-6" end - def nav_text_class_for(path) - return "text-red" if path.any? { |p| current_page?(p) } + def nav_text_class_for(paths) + return "text-red" if paths.any? { |p| active_path?(p) } "text-grey-400" end + def active_path?(path) + return request.path == path if root_path == path + + request.path.starts_with?(path) + end + + def title(title) + content_for :title, title + end + + def show_back_button? + current_page?(notification_settings_path) || + resource_show_page?("speakers") || + resource_show_page?("sessions") + end + + def back_title + if controller_name.include?("_") + controller_name.humanize + else + "#{controller_name.singularize.capitalize} detail" + end + end + def show_header? - !current_page?(new_user_session_path) && + (user_signed_in? || !current_page?(unauthenticated_root_path)) && + !current_page?(new_user_session_path) && !current_page?(about_path) && - !current_page?(coming_soon_path) + !current_page?(coming_soon_path) && + !show_back_button? end def show_bottom_navbar? - user_signed_in? && - !current_page?(coming_soon_path) + current_page?(sessions_path) || + (user_signed_in? || !current_page?(unauthenticated_root_path)) && + !bottom_navbar_excluded_paths.any? { |path| current_page?(path) } end - # Todo: A better approach would be to support authenticated root and unauthenticated root in routes.rb - def homepage_link - user_signed_in? ? root_path : new_user_session_path + def show_bookmark_button?(session) + return true if controller_name == "schedules" + + !session.past? end - def title(title) - content_for :title, title + private + + def bottom_navbar_excluded_paths + [ + new_user_session_path, + new_registration_path, + new_password_reset_path, + edit_password_reset_path, + post_submit_password_reset_path + ] + end + + def resource_show_page?(resource) + controller_name == resource && action_name == "show" end end diff --git a/app/helpers/session_helper.rb b/app/helpers/session_helper.rb index 2e38a062..22e08529 100644 --- a/app/helpers/session_helper.rb +++ b/app/helpers/session_helper.rb @@ -4,7 +4,7 @@ def session_filter_color(scope) when "live" "bg-green-500" when "past" - "bg-blue-teal-light" + "bg-bluegray-200" when "starting_soon" "bg-yellow" end @@ -19,4 +19,24 @@ def session_anchor(session) session.starts_at&.strftime("%Y-%m-%d-%H-%M") end + + def current_day_anchor(session_date) + return "" if session_date != Date.current || current_agenda_session.blank? + + "#" + session_anchor(current_agenda_session) + end + + def current_starts_at_filter + return unless current_conference + + (current_conference.sessions.starts_at(Date.current).first || current_conference.sessions.order(:starts_at).first)&.starts_at&.to_date + end + + def current_agenda_session + @current_agenda_session ||= current_conference&.sessions&.live_or_upcoming_today&.first + end + + def current_schedule_session + @current_schedule_session ||= current_user&.sessions&.live_or_upcoming_today&.first + end end diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 632444b6..5ac2df52 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -5,9 +5,11 @@ import { application } from 'controllers/application' // Eager load all controllers defined in the import map under controllers/**/*_controller import { eagerLoadControllersFrom } from '@hotwired/stimulus-loading' import Dropdown from '@stimulus-components/dropdown' +import PasswordVisibility from '@stimulus-components/password-visibility' // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" // lazyLoadControllersFrom("controllers", application) eagerLoadControllersFrom('controllers', application) application.register('dropdown', Dropdown) +application.register('password-visibility', PasswordVisibility) diff --git a/app/mailers/password_mailer.rb b/app/mailers/password_mailer.rb index bfb7aa8d..3457a94c 100644 --- a/app/mailers/password_mailer.rb +++ b/app/mailers/password_mailer.rb @@ -1,5 +1,5 @@ class PasswordMailer < ApplicationMailer def password_reset - mail to: params[:user].email + mail(to: params[:user].email, subject: I18n.t("mailers.password_mailer.password_reset.subject")) end end diff --git a/app/mailers/session_mailer.rb b/app/mailers/session_mailer.rb index 46e5dab0..ca8c4969 100644 --- a/app/mailers/session_mailer.rb +++ b/app/mailers/session_mailer.rb @@ -1,10 +1,12 @@ class SessionMailer < ApplicationMailer helper :datetime + def reminder @recipient = params[:recipient] @session = params[:record] @speakers = @session.speakers @notification = params[:notification] - mail(to: @recipient.email, subject: default_i18n_subject) + + mail(to: @recipient.email, subject: @notification&.subject) end end diff --git a/app/models/conference.rb b/app/models/conference.rb index 201d2010..6aaf98b9 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -18,7 +18,7 @@ def self.ransackable_attributes(_auth_object = nil) %w[name] end - def session_dates + def dates sessions.distinct.pluck(Arel.sql("date(starts_at)")).map(&:to_date).sort end end diff --git a/app/models/session.rb b/app/models/session.rb index eced1f1f..6e0038b0 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -4,6 +4,7 @@ # # id :integer not null, primary key # ends_at :datetime not null +# public :boolean default(TRUE), not null # sent_reminders :json # slug :string # starts_at :datetime not null @@ -16,8 +17,10 @@ # Indexes # # index_sessions_on_conference_id (conference_id) +# index_sessions_on_ends_at (ends_at) # index_sessions_on_location_id (location_id) # index_sessions_on_slug (slug) UNIQUE +# index_sessions_on_starts_at (starts_at) # class Session < ApplicationRecord extend FriendlyId @@ -48,6 +51,9 @@ class Session < ApplicationRecord scope :live, -> { where("? BETWEEN starts_at AND ends_at", Time.current) } scope :starting_soon, -> { where("starts_at BETWEEN ? and ?", Time.current, 1.hour.from_now) } scope :upcoming_today, -> { where("date(starts_at) = ? and starts_at > ?", Date.current, Time.current) } + scope :live_or_upcoming_today, -> { live.or(upcoming_today) } + scope :publics, -> { where(public: true) } + scope :privates, -> { where(public: false) } def self.ransackable_attributes(_auth_object = nil) %w[title] @@ -66,4 +72,8 @@ def starting_soon? def past? ends_at < Time.current end + + def private? + !public? + end end diff --git a/app/models/user.rb b/app/models/user.rb index 1de932ce..4a4dd983 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -32,6 +32,7 @@ class User < ApplicationRecord validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP} validates :password_digest, presence: true validates :password, length: {minimum: 8}, if: -> { password.present? } + validate :validate_tester_user after_create_commit { create_profile! } @@ -44,4 +45,12 @@ class User < ApplicationRecord def self.ransackable_attributes(_auth_object = nil) %w[email] end + + private + + def validate_tester_user + if Feature.enabled?(:only_tester_registration) + errors.add(:base, "Only tester users are allowed to sign up") unless email.include?("+tester") + end + end end diff --git a/app/notifiers/session_reminder_notifier.rb b/app/notifiers/session_reminder_notifier.rb index ab6cba36..c7e4a4f4 100644 --- a/app/notifiers/session_reminder_notifier.rb +++ b/app/notifiers/session_reminder_notifier.rb @@ -28,6 +28,18 @@ def title end end + def subject + t("mailers.session_mailer.reminder.subject", title: record.title) + end + + def email_title + if params[:time_before_session].match?(/^0\s/) + t("mailers.session_mailer.reminder.title.without_time") + else + t("mailers.session_mailer.reminder.title.with_time", time_before_session: params[:time_before_session]) + end + end + def delivered_at time_difference = distance_of_time_in_words(Time.current, created_at, scope: "date_time.distance_in_words.short") (time_difference == "now") ? time_difference : "#{time_difference} ago" diff --git a/app/queries/session_query.rb b/app/queries/session_query.rb index 7807e336..8902a28f 100644 --- a/app/queries/session_query.rb +++ b/app/queries/session_query.rb @@ -9,6 +9,7 @@ def initialize(relation: Session.all, params: {}) def call filter_by_date filter_by_status + filter_privates relation end @@ -27,6 +28,12 @@ def filter_by_status self.relation = relation.send_chain_or(status_scopes) end + def filter_privates + return if params[:show_private].present? + + self.relation = relation.publics + end + def starts_at @_starts_at ||= params[:starts_at]&.to_date rescue Date::Error diff --git a/app/views/abouts/show.html.erb b/app/views/abouts/show.html.erb index 9629a974..efbc6f1f 100644 --- a/app/views/abouts/show.html.erb +++ b/app/views/abouts/show.html.erb @@ -1,18 +1,19 @@ <%= title "About - Rails World 2024" %> -
-
-
+
+
+
+

Brought to you by

<%= inline_svg_tag "telos_labs_logo.svg", class: "mb-10 w-full" %> -

+

We designed and developed this open-source app for the Rails community.
- You can contribute to the repo here. + You can contribute to the repo here.

-

Telos creates disruptive, purpose-driven software for visionary clients to enhance productivity and enrich lives.

+

Telos creates disruptive, purpose-driven software for visionary clients to enhance productivity and enrich lives.

<%= inline_svg_tag "icon_web.svg", class: "w-6 h-6" %> diff --git a/app/views/attendees/create.turbo_stream.erb b/app/views/attendees/create.turbo_stream.erb new file mode 100644 index 00000000..eb544f0b --- /dev/null +++ b/app/views/attendees/create.turbo_stream.erb @@ -0,0 +1,18 @@ +<%= turbo_stream.replace dom_id(@session, "bookmark") do %> + <%= render( + partial: 'sessions/bookmark', + locals: { + session: @session, + user_is_an_attendee: @user_session_ids.include?(@session.id) + } + ) %> +<% end %> + +<%= turbo_stream.append "flash_message" do %> + <%= render( + partial: "layouts/flash_message", + locals: { + message: I18n.t("controllers.attendees.add_user.notice") + } + )%> +<% end %> diff --git a/app/views/attendees/destroy.turbo_stream.erb b/app/views/attendees/destroy.turbo_stream.erb new file mode 100644 index 00000000..21fc1550 --- /dev/null +++ b/app/views/attendees/destroy.turbo_stream.erb @@ -0,0 +1,20 @@ +<%= turbo_stream.replace dom_id(@session, "bookmark") do %> + <%= render( + partial: 'sessions/bookmark', + locals: { + session: @session, + user_is_an_attendee: @user_session_ids.include?(@session.id) + } + ) %> +<% end %> + +<%= turbo_stream.remove dom_id(@session, "schedule")%> + +<%= turbo_stream.append "flash_message" do %> + <%= render( + partial: "layouts/flash_message", + locals: { + message: I18n.t("controllers.attendees.remove_user.notice") + } + )%> +<% end %> diff --git a/app/views/layouts/_apple_splash_screen.html.erb b/app/views/layouts/_apple_splash_screen.html.erb index 6644fc7b..3d737e75 100644 --- a/app/views/layouts/_apple_splash_screen.html.erb +++ b/app/views/layouts/_apple_splash_screen.html.erb @@ -24,3 +24,30 @@ " media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)"> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/views/layouts/_back_button.html.erb b/app/views/layouts/_back_button.html.erb new file mode 100644 index 00000000..1d5c633b --- /dev/null +++ b/app/views/layouts/_back_button.html.erb @@ -0,0 +1,9 @@ +
+
+ <%= link_to "javascript:history.back()", class: "absolute left-0 flex items-center py-2" do %> + <%= inline_svg_tag("icons/chevron_left.svg", size: "24", class: "fill-red") %> + Back + <% end %> +

<%= back_title %>

+
+
diff --git a/app/views/layouts/_bottom_navbar.html.erb b/app/views/layouts/_bottom_navbar.html.erb index 2d7e7138..bd0f59fb 100644 --- a/app/views/layouts/_bottom_navbar.html.erb +++ b/app/views/layouts/_bottom_navbar.html.erb @@ -1,75 +1,74 @@ -
-
-
- <%= link_to( - sessions_path( - starts_at: current_conference.sessions.upcoming_today.first&.starts_at&.to_date - ), - class: [ - nav_text_class_for([sessions_path, root_path]), - "flex flex-col items-center justify-center" - ] - ) do %> - <%= inline_svg_tag "icons/calendar.svg", class: nav_icon_class_for([sessions_path, root_path]) %> - Agenda - <% end %> +
+
+ <%= link_to( + sessions_path( + starts_at: current_starts_at_filter, + anchor: session_anchor(current_agenda_session) + ), + class: [ + nav_text_class_for([sessions_path, root_path, "/speakers"]), + "flex flex-col items-center justify-center" + ] + ) do %> + <%= inline_svg_tag "icons/calendar.svg", class: nav_icon_class_for([sessions_path, root_path, "/speakers"]) %> + Agenda + <% end %> - <%= link_to( - schedule_path( - starts_at: current_user.sessions.upcoming_today.first&.starts_at&.to_date, - anchor: session_anchor(current_user.sessions.upcoming_today.first) - ), - class: [ - nav_text_class_for([schedule_path]), - "flex flex-col items-center justify-center" - ] - ) do %> - <%= inline_svg_tag "icons/clock.svg", class: nav_icon_class_for([schedule_path]) %> - My Schedule - <% end %> + <%= link_to( + schedule_path( + starts_at: current_starts_at_filter, + anchor: session_anchor(current_schedule_session) + ), + class: [ + nav_text_class_for([schedule_path]), + "flex flex-col items-center justify-center" + ] + ) do %> + <%= inline_svg_tag "icons/clock.svg", class: nav_icon_class_for([schedule_path]) %> + My Schedule + <% end %> - <%= link_to( - profile_path(current_profile&.uuid), - class: [ - nav_text_class_for([profile_path(current_profile&.uuid), edit_profile_path(current_profile&.uuid)]), - "flex flex-col items-center justify-center" - ] - ) do %> - <%= inline_svg_tag( - "icons/avatar_no_fill.svg", - class: nav_icon_class_for([profile_path(current_profile&.uuid), edit_profile_path(current_profile&.uuid)]) + <%= link_to( + user_signed_in? ? profile_path(current_profile&.uuid) : new_user_session_path, + class: [ + nav_text_class_for(["/profiles"]), + "flex flex-col items-center justify-center" + ] + ) do %> + <%= inline_svg_tag( + "icons/avatar_no_fill.svg", + class: nav_icon_class_for(["/profiles"]) ) %> - Profile - <% end %> + Profile + <% end %> - <%= link_to( - notifications_path, - class: [ - nav_text_class_for([notifications_path, notification_settings_path]), - "relative flex flex-col items-center justify-center" - ] - ) do %> - <%= inline_svg_tag "icons/bell.svg", class: nav_icon_class_for([notifications_path, notification_settings_path]) %> - <% if unread_notifications.present? %> -
- - <%= unread_notifications.count < 10 ? unread_notifications.count : "+9" %> - -
- <% end %> - Notifications + <%= link_to( + notifications_path, + class: [ + nav_text_class_for([notifications_path, notification_settings_path]), + "relative flex flex-col items-center justify-center" + ] + ) do %> + <%= inline_svg_tag "icons/bell.svg", class: nav_icon_class_for([notifications_path, notification_settings_path]) %> + <% if unread_notifications.present? %> +
+ + <%= unread_notifications.length < 10 ? unread_notifications.length : "+9" %> + +
<% end %> + Notifications + <% end %> - <%= link_to( - about_path, - class: [ - nav_text_class_for([about_path]), - "flex flex-col items-center justify-center" - ] - ) do %> - <%= inline_svg_tag "icons/info_circle.svg", class: nav_icon_class_for([about_path]) %> - About - <% end %> -
+ <%= link_to( + about_path, + class: [ + nav_text_class_for([about_path]), + "flex flex-col items-center justify-center" + ] + ) do %> + <%= inline_svg_tag "icons/info_circle.svg", class: nav_icon_class_for([about_path]) %> + About + <% end %>
diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index a89b11ef..9e1fb269 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -1,7 +1,10 @@ -
+
- <%= link_to homepage_link do %> + <%= link_to root_path( + starts_at: current_starts_at_filter, + anchor: session_anchor(current_agenda_session) + ) do %> <%= inline_svg_tag("main_logo.svg", class: "w-full") %> <% end %>
-
+ diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9ec6b98b..17c65dba 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,9 +1,9 @@ - + <%= content_for(:title).presence || "Rails World 2024" %> - + @@ -34,18 +34,26 @@ data-web-push-notifications-vapid-key-value=#{vapid_public_key} data-web-push-notifications-notifications-enabled-value=#{current_profile.web_push_notifications}" : '' %>> -
+
+ <% if show_back_button? %> + <%= render partial: "layouts/back_button" %> + <% end %> + <% if show_header? %> <%= render partial: "layouts/header" %> <% end %> +
- <%= render_notice_flash %> +
+
+ <%= render_notice_flash %> +
<%= yield %> - - <% if show_bottom_navbar? %> - <%= render partial: "layouts/bottom_navbar", locals: { unread_notifications: current_user.notifications.unread } %> - <% end %>
+ + <% if show_bottom_navbar? %> + <%= render partial: "layouts/bottom_navbar", locals: { unread_notifications: current_user&.notifications&.unread } %> + <% end %> diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index a10268c7..071e1fef 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -62,6 +62,7 @@ font-weight: 900; text-align: center; line-height: 32px; + object-fit: cover; } .c-event-card__speaker-title{ @@ -69,7 +70,6 @@ font-style: italic; font-weight: 900; line-height: 20px; - color: #CB0C1C; } .c-event-card__speaker-subtitle{ @@ -79,7 +79,7 @@ line-height: 14px; } - .c-event-card__title{ + .c-event-card__title, a{ font-size: 20px; font-style: italic; font-weight: 900; @@ -123,10 +123,15 @@ margin-bottom: 12px; } - .c-event-card__time-icon{ - width: 16px; + .c-event-card__time-icon { height: 16px; margin-right: 4px; + text-align: center; + } + + .c-event-card__time-icon-img { + width: 16px; + height: 16px; } .c-event-card__time-item{ @@ -149,6 +154,10 @@ text-align: center; } + .c-footer-img { + width: 328px; + } + .c-unsubscribe-notifications{ display: block; text-align: center; @@ -171,6 +180,32 @@ text-decoration-line: underline; } + .c-paragraph { + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 20px; + color: #1C1C1E; + margin-bottom: 16px; + } + + .c-button { + display: flex; + padding: 16px 10px; + margin: 0 auto; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 10px; + background-color: #CB0C1C; + font-size: 18px; + line-height: 22px; + font-weight: 600; + font-style: italic; + color: white; + text-decoration: none; + text-transform: uppercase; + } diff --git a/app/views/notification_settings/show.html.erb b/app/views/notification_settings/show.html.erb index 4722bdb4..77b8b1c6 100644 --- a/app/views/notification_settings/show.html.erb +++ b/app/views/notification_settings/show.html.erb @@ -4,10 +4,10 @@ model: current_user.profile, url: notification_settings_path, method: :put, - class: "flex flex-col items-center w-full p-5", + class: "flex flex-col items-center h-full w-full px-5 pt-safe-area-back-button pb-safe-area-bottom-navbar", data: {controller: 'form', form_target: 'form'} ) do |form| %> -
+

Notifications Settings

diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index cbc8f513..45328e30 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -7,8 +7,9 @@ ) do %>
<% if speaker&.image&.attached? %> -
- <%= image_tag url_for(speaker.image), class: "rounded-full" %> +
+
+
<%= inline_svg_tag "icon_notification.svg", class: "absolute z-10 bottom-[-4px] right-[-4px]" %>
<% else %> @@ -21,8 +22,12 @@
-

<%= notification.title %>

-   · <%= notification.delivered_at %> +

+ <%= notification.title %> + +   · <%= notification.delivered_at %> + +

<%= notification.record.title %>

diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index 6e66e1f8..556ccc0c 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -1,8 +1,8 @@ <%= title "Notifications - Rails World 2024" %> -
-
-

Notifications

+
+
+

Notifications

<% if @notifications.present? %>
diff --git a/app/views/password_mailer/password_reset.html.erb b/app/views/password_mailer/password_reset.html.erb index 45954945..cbd383d8 100644 --- a/app/views/password_mailer/password_reset.html.erb +++ b/app/views/password_mailer/password_reset.html.erb @@ -1 +1,27 @@ -<%= link_to "Reset your password", edit_password_reset_url(token: params[:token]) %> +
+
+

+ Reset your password +

+ +

+ Hello, +

+ we're sending you this email because you requested a password reset. +

+ Click on the button to create a new password: +

+ + <%= link_to "Create new password", edit_password_reset_url(token: params[:token]), class: "c-button" %> +
+ + If you didn't request a password reset, you can ignore this email. +

+
+ + +
diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb index 97dff55a..c8690f26 100644 --- a/app/views/password_resets/edit.html.erb +++ b/app/views/password_resets/edit.html.erb @@ -1,5 +1,5 @@ -
- <%= form_with model: @user, url: password_reset_path(token: params[:token]), class: "flex flex-col h-full justify-between w-full max-w-screen-sm" do |form| %> +
+ <%= form_with model: @user, url: password_reset_path(token: params[:token]), class: "flex flex-col h-full justify-between w-full max-w-screen-sm pt-10" do |form| %>

Reset your password

diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb index 67971293..a24e27e2 100644 --- a/app/views/password_resets/new.html.erb +++ b/app/views/password_resets/new.html.erb @@ -1,5 +1,5 @@ -
- <%= form_with model: @user, url: password_reset_path, class: "flex flex-col h-full w-full max-w-screen-sm justify-between" do |form| %> +
+ <%= form_with model: @user, url: password_reset_path, class: "flex flex-col h-full w-full max-w-screen-sm justify-between pt-10" do |form| %>

Forgot Password

diff --git a/app/views/password_resets/post_submit.html.erb b/app/views/password_resets/post_submit.html.erb index 337ce8b7..809d66a1 100644 --- a/app/views/password_resets/post_submit.html.erb +++ b/app/views/password_resets/post_submit.html.erb @@ -1,5 +1,5 @@ -
-
+
+

Check your inbox

We've sent you a link to recover your password to the email you provided.

diff --git a/app/views/profiles/edit.html.erb b/app/views/profiles/edit.html.erb index 5d6d3183..84a712ed 100644 --- a/app/views/profiles/edit.html.erb +++ b/app/views/profiles/edit.html.erb @@ -6,9 +6,9 @@ <% end %> -
-
-

Set up your account

+
+
+

Set up your account

<%= form_with model: @profile, url: profile_path(@profile.uuid), class: "flex flex-col items-center" do |form| %>
@@ -52,7 +52,7 @@ <%= form.text_field :name, class: "w-full mb-6", placeholder: "John P. Doe" %> <%= form.label :bio, class: "font-bold text-white text-base mb-2" %> - <%= form.text_area :bio, class: "w-full mb-6", placeholder: "I'm a Ruby on Rails developer with six years of experience. Outside of work, I enjoy hiking and using dating apps." %> + <%= form.text_area :bio, class: "w-full mb-6", placeholder: "I'm a Ruby on Rails developer with six years of experience. Outside of work, I enjoy hiking and using dating apps.", rows: 3 %> <%= form.label :job_title, class: "font-bold text-white text-base mb-2" %> <%= form.text_field :job_title, class: "w-full mb-6", placeholder: "Senior Developer at Telos Labs" %> @@ -96,7 +96,11 @@
<%= form.submit "SAVE PROFILE", class:"w-full bg-red rounded-[10px] p-4 text-lg font-black text-white cursor-pointer" %> - <%= link_to "Skip for now", root_path, class: "text-lg underline font-black italic text-white mt-10" %> + <%= link_to "Edit your email and password", edit_user_path, class: "text-lg underline font-black italic text-white mt-10" %> + + <% if current_user.created_at > 1.minute.ago %> + <%= link_to "Skip for now", root_path, class: "text-lg underline font-black italic text-white mt-2" %> + <% end %> <% end %>
diff --git a/app/views/profiles/show.html.erb b/app/views/profiles/show.html.erb index a40960ce..c3f9b2d6 100644 --- a/app/views/profiles/show.html.erb +++ b/app/views/profiles/show.html.erb @@ -2,12 +2,12 @@ <%= title "#{profile_name}'s Profile - Rails World 2024" %> -
-
-
+
+
+
<% if @profile.image.attached? %> -
+
<% else %>
@@ -22,14 +22,14 @@
<% if @profile.bio.present? %> -

<%= @profile.bio %>

+

<%= @profile.bio %>

<% end %> -
+
<%= link_to( @profile.github_url, target: "_blank", class: [ - "flex flex-row grow items-center justify-center w-28 h-14 rounded-lg mt-6", + "flex flex-row grow items-center justify-center max-w-28 w-full h-14 rounded-lg mt-6", "bg-red": @profile.github_url.present?, "bg-grey-300 cursor-not-allowed": @profile.github_url.blank? ] @@ -40,7 +40,7 @@ @profile.twitter_url, target: "_blank", class: [ - "flex flex-row grow items-center justify-center w-28 h-14 rounded-lg mt-6", + "flex flex-row grow items-center justify-center max-w-28 w-full h-14 rounded-lg mt-6", "bg-red": @profile.twitter_url.present?, "bg-grey-300 cursor-not-allowed": @profile.twitter_url.blank? ] @@ -51,7 +51,7 @@ @profile.linkedin_url, target: "_blank", class: [ - "flex flex-row grow items-center justify-center w-28 h-14 rounded-lg mt-6", + "flex flex-row grow items-center justify-center max-w-28 w-full h-14 rounded-lg mt-6", "bg-red": @profile.linkedin_url.present?, "bg-grey-300 cursor-not-allowed": @profile.linkedin_url.blank? ] @@ -64,7 +64,12 @@ <% if allowed_to?(:edit?, @profile, with: ProfilePolicy) %>
- <%= render inline: @profile.svg_qr_code(module_size: 4) if @profile.is_public? %> + <% if @profile.is_public? %> +
+ <%= render inline: @profile.svg_qr_code(module_size: 4) %> +
+ <% end %> + <%= link_to( 'Edit Profile', edit_profile_path(@profile.uuid), class: "text-red font-bold rounded-sm underline italic text-lg" @@ -72,7 +77,7 @@ <%= button_to( "Log out", user_session_path, method: :delete, form_class: "w-full max-w-screen-sm px-4", - class: "w-full bg-red py-4 text-white font-black italic rounded-[10px] uppercase mb-6" + class: "w-full bg-white py-4 text-red font-black italic rounded-[10px] uppercase" ) %>
<% end %> diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb index e9ff6ad6..f3b5d681 100644 --- a/app/views/registrations/new.html.erb +++ b/app/views/registrations/new.html.erb @@ -1,5 +1,5 @@ -
- <%= form_with model: @user, url: registration_path, class: "flex flex-col h-full justify-between w-full max-w-screen-sm" do |form| %> +
+ <%= form_with model: @user, url: registration_path, class: "flex flex-col h-full justify-between w-full max-w-screen-sm pt-10" do |form| %>

Sign Up

@@ -18,7 +18,7 @@
<%= form.label :password, class: "text-white italic font-bold mb-2" %> <%= form.password_field :password, required: true, data: { test_id: "password_field" }, placeholder: "You know how a strong password goes, right?" %> -

Password should contain at least 8 characters

+

Password should contain at least 8 characters

diff --git a/app/views/schedules/show.html.erb b/app/views/schedules/show.html.erb index b300a399..3b66c2a1 100644 --- a/app/views/schedules/show.html.erb +++ b/app/views/schedules/show.html.erb @@ -1,34 +1,50 @@ <%= title "My Schedule - Rails World 2024" %> -
-
-
-

My Schedule

-
+
+
+
+
+

My Schedule

+
- <%= render partial: 'sessions/filters', locals: { resource: :schedule } %> + <%= render partial: 'sessions/filters', locals: { resource: :schedule } %> +
-
+
<% if @sessions.present? %> + <% previous_date = nil %> <% @sessions.group_by(&:starts_at).each do |starts_at, sessions| %> -
- +
<%= pluralize(sessions.length, "Session") %>
+ <% end %> <% sessions.each do |session| %> <%= render( partial: 'sessions/card', locals: { session: session, - user_is_an_attendee: true + user_is_an_attendee: true, + id_prefix: "schedule", } ) %> <% end %> + <% previous_date = starts_at.to_date %> <% end %> <% elsif params[:starts_at].present? || session_filter_params.present? %>
diff --git a/app/views/service_worker/offline.html.erb b/app/views/service_worker/offline.html.erb new file mode 100644 index 00000000..a2d0addc --- /dev/null +++ b/app/views/service_worker/offline.html.erb @@ -0,0 +1,11 @@ +
+
+ <%= inline_svg_tag "icons/no_wifi.svg", size: "56", class: "fill-gray-400" %> +
+

You're offline

+

Check your connection
and try again.

+ <%= link_to( + 'Refresh the page', "", + class: "text-red font-bold text-center rounded-sm underline italic text-lg" + ) %> +
diff --git a/app/views/service_worker/service_worker.js b/app/views/service_worker/service_worker.js index ebafb89d..e9f43301 100644 --- a/app/views/service_worker/service_worker.js +++ b/app/views/service_worker/service_worker.js @@ -1,4 +1,10 @@ -/* global self, clients */ +/* global self, clients, importScripts, workbox */ + +importScripts( + 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js' +) + +// ------------- Push Notifications ------------- self.addEventListener('push', async (event) => { let data @@ -41,3 +47,51 @@ self.addEventListener('notificationclick', function (event) { }) ) }) + +// ------------- Request handling ------------- + +const { CacheFirst, NetworkFirst } = workbox.strategies +const { registerRoute } = workbox.routing + +// For assets (scripts and images), cache first +registerRoute( + ({ request }) => request.destination === 'script' || + request.destination === 'style', + new CacheFirst({ + cacheName: 'assets-styles-and-scripts' + }) +) + +registerRoute( + ({ request }) => request.destination === 'image', + new CacheFirst({ + cacheName: 'assets-images' + }) +) + +// For pages, network first +registerRoute( + ({ request, url }) => request.destination === 'document' || + request.destination === '', + new NetworkFirst() +) + +// Offline fallback + +const { warmStrategyCache } = workbox.recipes +const { setCatchHandler } = workbox.routing +const strategy = new NetworkFirst() +const urls = ['/offline.html'] + +// Warm the runtime cache with a list of asset URLs +warmStrategyCache({ urls, strategy }) + +// Trigger a 'catch' handler when any of the other routes fail to generate a response +setCatchHandler(async ({ event }) => { + switch (event.request.destination) { + case 'document': + return strategy.handle({ event, request: urls[0] }) + default: + return Response.error() + } +}) diff --git a/app/views/session_mailer/reminder.html.erb b/app/views/session_mailer/reminder.html.erb index 2add3464..ce9e243f 100644 --- a/app/views/session_mailer/reminder.html.erb +++ b/app/views/session_mailer/reminder.html.erb @@ -1,17 +1,21 @@
-

Your bookmarked session is <%= @notification.title.downcase %>

+

<%= @notification&.email_title %>

- <%= @session.title %> + <%= link_to @session.title, sessions_url, style: "color: #CB0C1C; text-decoration: none;" %>
<% if @speakers.present? %> <% @speakers.each do |speaker| %>
-
- <%= speaker.name.split.take(2).map { |word| word[0].upcase }.join %> -
+ <% if speaker.image&.attached? %> + <%= image_tag speaker.image, class: "c-event-card__speaker-img" %> + <% else %> +
+ <%= speaker.name.split.take(2).map { |word| word[0].upcase }.join %> +
+ <% end %>
<%= speaker.name %>
<%= speaker.job_title %>
@@ -35,25 +39,20 @@ <% end %>
- <%#
-
%> -
- <%= @session.starts_at.strftime("%A, %b %d") %> +
+ <%= image_tag url_for("icons/calendar.png"), class: "c-event-card__time-icon-img" %>
-
- -
- <%#
-
%>
- <%= @session.starts_at.strftime("%H:%M") %> - <%= @session.ends_at.strftime("%H:%M") %> + <%= @session.starts_at&.strftime("%b %d") %> - + <%= @session.starts_at&.strftime("%H:%M") %> to <%= @session.ends_at&.strftime("%H:%M") %>
<% if @session.location.present? %>
- <%#
-
%> +
+ <%= image_tag url_for("icons/location.png"), class: "c-event-card__time-icon-img" %> +
<%= @session.location.name %>
@@ -62,7 +61,9 @@

You are receiving this email because your notification settings are configured to receive communications. If you wish to stop receiving these emails, you can edit your settings. diff --git a/app/views/sessions/_bookmark.html.erb b/app/views/sessions/_bookmark.html.erb new file mode 100644 index 00000000..73e0ecbc --- /dev/null +++ b/app/views/sessions/_bookmark.html.erb @@ -0,0 +1,21 @@ +<% if show_bookmark_button?(session) %> + <% if user_is_an_attendee %> + <%= button_to( + session_attendee_path(session_id: session.id, params: request.params[:starts_at], anchor: session_anchor(session)), + method: :delete, form: { class: "z-10 flex items-center", id: dom_id(session, "bookmark") } + ) do %> +

+ <%= inline_svg_tag("icons/bookmark-full.svg") %> +
+ <% end %> + <% else %> + <%= button_to( + session_attendee_path(session_id: session.id, params: request.params[:starts_at], anchor: session_anchor(session)), + form: { class: "z-10 flex items-center", id: dom_id(session, "bookmark"), } + ) do %> +
+ <%= inline_svg_tag("icons/bookmark-outline.svg", class: "") %> +
+ <% end %> + <% end %> +<% end %> diff --git a/app/views/sessions/_card.html.erb b/app/views/sessions/_card.html.erb index 486ac1bb..7b9240a2 100644 --- a/app/views/sessions/_card.html.erb +++ b/app/views/sessions/_card.html.erb @@ -1,95 +1,76 @@ -<%# locals: (session:, user_is_an_attendee:) %> +<%# locals: (session:, user_is_an_attendee:, id_prefix: nil) %> -
-
- <%= render partial: "sessions/status", locals: { session: session } %> +
+ -
- -

- <%= session.title %> -

-
+ <%= render partial: "sessions/status", locals: { session: session } %> - <% if user_is_an_attendee %> - <%= button_to( - session_attendee_path(session_id: session.id, params: request.params[:starts_at]), - method: :delete, form_class: "flex items-center" - ) do %> -
- <%= inline_svg_tag("icons/bookmark-full.svg") %> -
- <% end %> - <% else %> - <%= button_to( - session_attendee_path(session_id: session.id, params: request.params[:starts_at]), - form_class: "flex items-center" +
+ +

+ <%= session.title %> +

+
+ <%= render partial: "sessions/bookmark", locals: { session: session, user_is_an_attendee: } %> +
+ + <% if session.speakers.present? %> +
+ <% session.speakers.each do |speaker| %> + <%= link_to( + speaker_path(speaker), + class: [ + "z-10 inline-flex items-center py-2 px-4 first:pl-0 rounded-lg min-w-fit", + "group hover:bg-[#FAE7E8]/[0.5] focus:bg-[#FAE7E8]/[0.5]" + ] ) do %> -
- <%= inline_svg_tag("icons/bookmark-outline.svg", class: "") %> -
- <% end %> - <% end %> -
+ <% if speaker.image&.attached? %> +
+
+ <% else %> + + <%= inline_svg_tag "icons/user_avatar.svg" %> + + <% end %> - <% if session.speakers.present? %> -
- <% session.speakers.each do |speaker| %> - <%= link_to( - speaker_path(speaker), + <%= content_tag( + :div, class: [ - "inline-flex items-center py-2 px-4 first:pl-0 rounded-lg min-w-fit", - "group hover:bg-[#FAE7E8]/[0.5] focus:bg-[#FAE7E8]/[0.5]" + "flex flex-col ml-2", + "max-w-[200px]": session.speakers.length > 1 ] ) do %> - <% if speaker.image&.attached? %> -
-
- <% else %> - - <%= inline_svg_tag "icons/user_avatar.svg" %> -
- <% end %> - - <%= content_tag( - :div, - class: [ - "flex flex-col ml-2", - "max-w-[200px]": session.speakers.count > 1 - ] - ) do %> -

<%= speaker.name %>

-

<%= speaker.job_title %>

- <% end %> +

<%= speaker.name %>

+

<%= speaker.job_title %>

<% end %> -
<% end %> -
- <% end %> - - - <% if session.tags.exists? %> -
- <% session.tags.each do |tag| %> -
- <%= tag.name %> -
- <% end %> -
+
<% end %> +
+ <% end %> -
- <%= inline_svg_tag("icons/calendar.svg", class: "w-[16px] h-[16px] mr-1 fill-grey-600") %> - - <%= session.starts_at&.strftime("%b %d") %> - - <%= session.starts_at&.strftime("%H:%M") %> to <%= session.ends_at&.strftime("%H:%M") %> - +
+ <% if session.tags.present? %> +
+ <% session.tags.each do |tag| %> +
+ <%= tag.name %> +
+ <% end %>
+ <% end %> -
- <%= inline_svg_tag("icons/location.svg", class: "w-[16px] h-[16px] mr-1 fill-grey-600") %> - <%= session.location&.name %> -
-
+
+ <%= inline_svg_tag("icons/calendar.svg", class: "w-[16px] h-[16px] mr-1 fill-grey-600") %> + + <%= session.starts_at&.strftime("%b %d") %> - + <%= session.starts_at&.strftime("%H:%M") %> to <%= session.ends_at&.strftime("%H:%M") %> + +
+ +
+ <%= inline_svg_tag("icons/location.svg", class: "w-[16px] h-[16px] mr-1 fill-grey-600") %> + <%= session.location&.name %> +
diff --git a/app/views/sessions/_filters.html.erb b/app/views/sessions/_filters.html.erb index 1349dd18..5dc86a91 100644 --- a/app/views/sessions/_filters.html.erb +++ b/app/views/sessions/_filters.html.erb @@ -1,20 +1,11 @@ <%# locals: (resource:) %> -
+
- <%= link_to( - 'All dates', - url_for([resource, session_filter_params.to_h]), - class: [ - "flex min-w-fit items-center justify-center px-4 py-2 rounded-full font-medium", - "bg-red text-white": params[:starts_at].blank?, - "bg-white text-red hover:bg-[#FAE7E8] focus:bg-[#FAE7E8]": params[:starts_at].present? - ] - ) %> - <% current_conference.session_dates.each do |session_date| %> + <% current_conference&.dates&.each do |session_date| %> <%= link_to( session_date.strftime("%a, %d"), - url_for([resource, session_filter_params.to_h.merge(starts_at: session_date)]), + url_for([resource, session_filter_params.to_h.merge(starts_at: session_date)]) + current_day_anchor(session_date), class: [ "flex min-w-fit items-center justify-center px-4 py-2 font-medium rounded-full", "bg-red text-white": params[:starts_at] == session_date.to_s, @@ -22,18 +13,27 @@ ] ) %> <% end %> + <%= link_to( + 'All dates', + url_for([resource, session_filter_params.to_h]), + class: [ + "flex min-w-fit items-center justify-center px-4 py-2 rounded-full font-medium", + "bg-red text-white": params[:starts_at].blank?, + "bg-white text-red hover:bg-[#FAE7E8] focus:bg-[#FAE7E8]": params[:starts_at].present? + ] + ) %>
<%= content_tag( :div, class: [ - "flex items-center justify-center ml-3 rounded-full min-w-[40px] min-h-[40px]", + "flex items-center justify-center ml-3 rounded-full min-w-[42px] min-h-[42px]", "bg-red fill-white": session_filter_params.present?, "bg-white fill-red hover:bg-[#FAE7E8] focus:bg-[#FAE7E8]": session_filter_params.blank? ] ) do %>
- @@ -79,7 +79,7 @@
<% if session_filter_params.present? %> -
+
Active filters:
diff --git a/app/views/sessions/_status.html.erb b/app/views/sessions/_status.html.erb index 9d24e774..0647bf54 100644 --- a/app/views/sessions/_status.html.erb +++ b/app/views/sessions/_status.html.erb @@ -20,10 +20,10 @@
<% elsif session.past? %> -
+
- + Session is over.
diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb index 390a7a17..6a122bee 100644 --- a/app/views/sessions/index.html.erb +++ b/app/views/sessions/index.html.erb @@ -1,34 +1,49 @@ <%= title "Agenda - Rails World 2024" %> -
-
-
-

Agenda

-
+
+
+
+
+

Agenda

+
- <%= render partial: 'sessions/filters', locals: { resource: :sessions } %> + <%= render partial: 'sessions/filters', locals: { resource: :sessions } %> +
-
+
<% if @sessions.present? %> + <% previous_date = nil %> <% @sessions.group_by(&:starts_at).each do |starts_at, sessions| %> -
- +
<%= pluralize(sessions.length, "Session") %>
+ <% end %> <% sessions.each do |session| %> <%= render( - partial: 'sessions/card', - locals: { - session: session, - user_is_an_attendee: @user_session_ids.include?(session.id) - } - ) %> + partial: 'sessions/card', + locals: { + session: session, + user_is_an_attendee: @user_session_ids&.include?(session.id) + } + ) %> <% end %> + <% previous_date = starts_at.to_date %> <% end %> <% elsif params[:starts_at].present? || session_filter_params.present? %>
diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb index 49c776c8..303994fe 100644 --- a/app/views/sessions/show.html.erb +++ b/app/views/sessions/show.html.erb @@ -1,27 +1,13 @@ <%= title "#{@session.title} - Rails World 2024" %> -
+
<%= render partial: "sessions/status", locals: { session: @session } %>

<%= @session.title %>

- <% if current_user.sessions.pluck(:id).include?(@session.id) %> - <%= button_to( - session_attendee_path(session_id: @session.id, params: request.params[:starts_at]), - method: :delete, form_class: "flex items-center ml-8" - ) do %> - <%= inline_svg_tag("icons/bookmark-full.svg") %> - <% end %> - <% else %> - <%= button_to( - session_attendee_path(session_id: @session.id, params: request.params[:starts_at]), - form_class: "flex items-center ml-8" - ) do %> - <%= inline_svg_tag("icons/bookmark-outline.svg") %> - <% end %> - <% end %> + <%= render partial: "bookmark", locals: { session: @session, user_is_an_attendee: @user_session_ids.include?(@session.id)} %>
<% if @session.speakers.present? %> @@ -45,7 +31,7 @@ <%= content_tag(:div, class: [ "flex flex-col ml-2", - "max-w-[200px]": @session.speakers.count > 1 + "max-w-[200px]": @session.speakers.length > 1 ] ) do %>

<%= speaker.name %>

<%= speaker.job_title %>

@@ -91,7 +77,7 @@

- <%= "About the #{'speaker'.pluralize(@session.speakers.count)}" %> + <%= "About the #{'speaker'.pluralize(@session.speakers.length)}" %>

<% @session.speakers.each do |speaker| %> diff --git a/app/views/speakers/show.html.erb b/app/views/speakers/show.html.erb index d4e5febc..6bb7b2cc 100644 --- a/app/views/speakers/show.html.erb +++ b/app/views/speakers/show.html.erb @@ -1,6 +1,6 @@ <%= title "#{@profile.name} - Rails World 2024" %> -
+
@@ -68,7 +68,7 @@ partial: "sessions/card", locals: { session: session, - user_is_an_attendee: Current.user.sessions.include?(session) + user_is_an_attendee: Current.user&.sessions&.include?(session) } ) %> <% end %> diff --git a/app/views/user_sessions/new.html.erb b/app/views/user_sessions/new.html.erb index cd294413..a321a3d0 100644 --- a/app/views/user_sessions/new.html.erb +++ b/app/views/user_sessions/new.html.erb @@ -1,4 +1,4 @@ -
+
<%= form_with model: @user, url: user_session_path, class: "flex flex-col h-full justify-between w-full max-w-screen-sm" do |form| %>
<%= inline_svg_tag("main_logo_with_date.svg", class: "w-full mb-10") %> @@ -15,14 +15,23 @@
- <%= form.label :password, class: "text-white italic font-bold mb-2" %> - <%= form.password_field :password, required: true, placeholder: "Password", data: { test_id: "password_field" } %> +
+ <%= form.label :password, class: "text-white italic font-bold mb-2" %> + <%= link_to "Forgot password?", new_password_reset_path, class: "font-black italic underline text-white" %> +
+
+ <%= form.password_field :password, required: true, class: "peer w-full", placeholder: "Password", data: { test_id: "password_field", "password-visibility-target": "input" } %> + +
- <%= link_to "Forgot password?", new_password_reset_path, class: "font-black italic underline text-white mb-10" %> - <%= link_to "Sign up", new_registration_path, class: "font-black italic underline text-white" %> + <%= link_to "View as guest", sessions_path, class: "font-black italic underline text-white mb-10" %> + <%= link_to "Sign up", new_registration_path, class: "font-black italic underline text-white mb-10" %>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb new file mode 100644 index 00000000..6e4eaa99 --- /dev/null +++ b/app/views/users/edit.html.erb @@ -0,0 +1,48 @@ +
+ <%= form_with model: Current.user, url: user_path, class: "flex flex-col h-full w-full max-w-screen-sm" do |form| %> +
+
+

User settings

+

Update your email, password or both.

+
+ +
+ <%= render partial: "shared/form_errors", locals: { errors: form.object.errors.full_messages } %> + + <%= form.label :email, class: "text-white italic font-bold mb-2" %> + <%= form.email_field :email, required: true, placeholder: "email@example.com" %> +

Fill this with a new email you want to use.

+ + <%= form.label :password_challenge, "Current Password", class: "text-white italic font-bold mb-2" %> +
+ <%= form.password_field :password_challenge, class: "peer w-full", placeholder: "Password", data: { "password-visibility-target": "input" } %> + +
+

Enter your current password if you want to create a new one.

+ + <%= form.label :password, "New Password", class: "text-white italic font-bold mb-2" %> +
+ <%= form.password_field :password, class: "peer w-full", placeholder: "Password", data: { "password-visibility-target": "input" } %> + +
+ + <%= form.label :password_confirmation, "New Password Confirmation", class: "text-white italic font-bold" %> +
+ <%= form.password_field :password_confirmation, class: "peer w-full", placeholder: "Password", data: { "password-visibility-target": "input" } %> + +
+
+
+ + <%= form.button "Update", type: "submit", class: "w-full bg-red py-4 text-white font-black italic rounded-[10px] uppercase mb-4" %> + <% end %> +
diff --git a/config/application.rb b/config/application.rb index 01990929..54329a28 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,7 @@ class Application < Rails::Application # # config.eager_load_paths << Rails.root.join("extras") + config.session_store :cookie_store, key: "_rails_world_session", expire_after: 1.month config.time_zone = "Eastern Time (US & Canada)" end end diff --git a/config/deploy.production.yml b/config/deploy.production.yml index 9f82cf0f..0efb64c1 100644 --- a/config/deploy.production.yml +++ b/config/deploy.production.yml @@ -23,6 +23,7 @@ env: - SESSION_REMINDERS_ENABLED - REGISTRATION_ENABLED - WEB_CONCURRENCY + - ONLY_TESTER_REGISTRATION_ENABLED accessories: vector: diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml index 02864cda..f36ebf59 100644 --- a/config/deploy.staging.yml +++ b/config/deploy.staging.yml @@ -22,6 +22,7 @@ env: - LITESTREAM_BACKUP_ENABLED - SESSION_REMINDERS_ENABLED - REGISTRATION_ENABLED + - ONLY_TESTER_REGISTRATION_ENABLED accessories: vector: diff --git a/config/environments/development.rb b/config/environments/development.rb index 382bc9c5..4a5f7064 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -45,6 +45,7 @@ config.action_mailer.raise_delivery_errors = true config.action_mailer.perform_caching = false config.action_mailer.default_url_options = {host: "localhost", port: 3000} + config.action_mailer.asset_host = "http://localhost:3000" # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/environments/production.rb b/config/environments/production.rb index 57ce7bbb..23f1f56a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -78,6 +78,7 @@ config.action_mailer.delivery_method = :mailpace config.action_mailer.mailpace_settings = {api_token: ENV["MAILPACE_API_TOKEN"]} config.action_mailer.default_url_options = {host: ENV["APP_HOST"]} + config.action_mailer.asset_host = ENV["APP_HOST"] # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 57ce7bbb..23f1f56a 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -78,6 +78,7 @@ config.action_mailer.delivery_method = :mailpace config.action_mailer.mailpace_settings = {api_token: ENV["MAILPACE_API_TOKEN"]} config.action_mailer.default_url_options = {host: ENV["APP_HOST"]} + config.action_mailer.asset_host = ENV["APP_HOST"] # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). diff --git a/config/importmap.rb b/config/importmap.rb index 2fb8a2af..4d309db2 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -10,3 +10,4 @@ pin_all_from "app/javascript/custom", under: "custom" pin "trix" pin "@rails/actiontext", to: "actiontext.esm.js" +pin "@stimulus-components/password-visibility", to: "@stimulus-components--password-visibility.js" # @3.0.0 diff --git a/config/locales/en.yml b/config/locales/en.yml index 019a974f..70f2410e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -24,6 +24,9 @@ en: alert: "Invalid email or password." destroy: notice: "You have been logged out." + users: + update: + success: "User has been updated successfully." attendees: add_user: notice: "Session was added to My Schedule" @@ -31,14 +34,27 @@ en: notice: "Session was removed from My Schedule" undo: expired: "The undo action has expired." + mailers: + session_mailer: + reminder: + subject: "%{title} is starting soon" + title: + with_time: "Your bookmarked session is starting in %{time_before_session}." + without_time: "Your bookmarked session is starting soon." + password_mailer: + password_reset: + subject: "Password Reset - Rails World 2024" views: status_filters: past: "Past" live: "Live" starting_soon: "Starting soon" submit: "Apply" + authentication: + unauthenticated: "You need to sign in or sign up before continuing." authorization: unauthorized: "You are not authorized to access this page." + invalid_auth_token: "Your request has expired. Please try again." session_mailer: reminder: subject: "This session is starting soon" @@ -59,4 +75,3 @@ en: one: "1month" other: "%{count}months" x_months: "%{count}months" - diff --git a/config/routes.rb b/config/routes.rb index fefaeb21..356954f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,15 @@ Rails.application.routes.draw do - root "sessions#index" + constraints AuthenticatedConstraint.new do + root "sessions#index" + end + + constraints UnauthenticatedConstraint.new do + root "user_sessions#new", as: :unauthenticated_root + end get "up" => "rails/health#show", :as => :rails_health_check get "/service-worker.js" => "service_worker#service_worker" + get "/offline.html" => "service_worker#offline" get "/manifest.json" => "service_worker#manifest" mount MissionControl::Jobs::Engine, at: "/jobs" @@ -10,6 +17,7 @@ resource :registration, only: [:new, :create] resource :user_session, only: [:new, :create, :destroy] + resource :user, only: [:edit, :update] resource :password, only: [:edit, :update] resource :password_reset, only: [:new, :create, :edit, :update] do get :post_submit diff --git a/config/tailwind.config.js b/config/tailwind.config.js index 74575f64..1281661f 100644 --- a/config/tailwind.config.js +++ b/config/tailwind.config.js @@ -33,15 +33,15 @@ module.exports = { 500: '#62C554' }, bluegray: { + 50: '#F4F7FB', + 200: '#CDD9EC', 600: '#829ECE' }, 'purple-dark': '#432463', 'purple-light': '#4E2A73', red: '#CB0C1C', 'blue-marine': '#003A5D', - 'blue-light': '#EFF6FF', 'blue-teal': '#00AFAA', - 'blue-teal-light': '#CDD9EC', yellow: '#F4BF4F' }, animation: { diff --git a/db/migrate/20240822153422_add_index_to_starts_and_ends_at_in_sessions.rb b/db/migrate/20240822153422_add_index_to_starts_and_ends_at_in_sessions.rb new file mode 100644 index 00000000..93ef5ef6 --- /dev/null +++ b/db/migrate/20240822153422_add_index_to_starts_and_ends_at_in_sessions.rb @@ -0,0 +1,6 @@ +class AddIndexToStartsAndEndsAtInSessions < ActiveRecord::Migration[7.2] + def change + add_index :sessions, :starts_at + add_index :sessions, :ends_at + end +end diff --git a/db/migrate/20240829165208_add_public_to_sessions.rb b/db/migrate/20240829165208_add_public_to_sessions.rb new file mode 100644 index 00000000..94d7c37e --- /dev/null +++ b/db/migrate/20240829165208_add_public_to_sessions.rb @@ -0,0 +1,5 @@ +class AddPublicToSessions < ActiveRecord::Migration[7.2] + def change + add_column :sessions, :public, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4f079492..d1df726e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_08_26_205135) do +ActiveRecord::Schema[7.2].define(version: 2024_08_29_165208) do create_table "action_text_rich_texts", force: :cascade do |t| t.string "name", null: false t.text "body" @@ -117,9 +117,12 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "slug" + t.boolean "public", default: true, null: false t.index ["conference_id"], name: "index_sessions_on_conference_id" + t.index ["ends_at"], name: "index_sessions_on_ends_at" t.index ["location_id"], name: "index_sessions_on_location_id" t.index ["slug"], name: "index_sessions_on_slug", unique: true + t.index ["starts_at"], name: "index_sessions_on_starts_at" end create_table "sessions_speakers", force: :cascade do |t| diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 3734f7f4..2aa6fdab 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -1,6 +1,5 @@ namespace :db do desc "Loading Rails World 2024 conference data." - task :rails_world_2024_seed, [:start_date] => :environment do |t, args| start_date = args[:start_date].present? ? Date.parse(args[:start_date]) : Date.new(2024, 9, 26) month = start_date.month @@ -13,55 +12,35 @@ namespace :db do conference = Conference.find_or_create_by!(name: "Rails World 2024") # Create Locations - track_1 = conference.locations.find_or_create_by!(name: "Track 1") - track_2 = conference.locations.find_or_create_by!(name: "Track 2") - lightning_track = conference.locations.find_or_create_by!(name: "Lightning Track") + track_1 = conference.locations.find_or_create_by!(name: "Track 1 (hosted by GitHub)") + track_2 = conference.locations.find_or_create_by!(name: "Track 2 (hosted by AppSignal)") + lightning_track = conference.locations.find_or_create_by!(name: "Lightning Track (hosted by Shopify)") sponsor_garden = conference.locations.find_or_create_by!(name: "Sponsor Garden") sponsor_lounge = conference.locations.find_or_create_by!(name: "Sponsor Lounge") break_location = conference.locations.find_or_create_by!(name: "Sponsor Garden, Shopify Lounge, Lightning Track, Kilns, Pavilions") - tbd_location = conference.locations.find_or_create_by!(name: "TBD") - tbd_attendees_location = conference.locations.find_or_create_by!(name: "TBD (will be shared with registered attendees)") - shopify_port = conference.locations.find_or_create_by!(name: "Shopify Port") + toronto_location = conference.locations.find_or_create_by!(name: "Toronto") + double_up_location = conference.locations.find_or_create_by!(name: "Double Up Lab") # Create Tags community = Tag.find_or_create_by!(name: "Community") - security = Tag.find_or_create_by!(name: "Security") - rails_8 = Tag.find_or_create_by!(name: "Rails 8") - productivity = Tag.find_or_create_by!(name: "Productivity") - tools = Tag.find_or_create_by!(name: "Tools") - developer_experience = Tag.find_or_create_by!(name: "Developer Experience") - deployment = Tag.find_or_create_by!(name: "Deployment") - performance = Tag.find_or_create_by!(name: "Performance") - storage_solutions = Tag.find_or_create_by!(name: "Storage Solutions") - refactoring = Tag.find_or_create_by!(name: "Refactoring") - database = Tag.find_or_create_by!(name: "Database") - hotwire = Tag.find_or_create_by!(name: "Hotwire") - ai = Tag.find_or_create_by!(name: "AI") - integrations = Tag.find_or_create_by!(name: "Integrations") - testing = Tag.find_or_create_by!(name: "Testing") - best_practices = Tag.find_or_create_by!(name: "Best Practices") - pwa = Tag.find_or_create_by!(name: "PWA") - insights = Tag.find_or_create_by!(name: "Insights") - background_jobs = Tag.find_or_create_by!(name: "Background Jobs") - rails_internals = Tag.find_or_create_by!(name: "Rails Internals") # Create Speakers @@ -336,24 +315,15 @@ namespace :db do end.profileable # Create Sessions - Session.find_or_create_by!( - conference: conference, - title: "Early registration, sponsored by Shopify", - starts_at: Time.zone.local(2024, month, registration_day, 16, 0) - ) do |session| - session.description = "Registration opens for those who are already in town. Avoid the Thursday morning line, get your badge, and grab a drink with other attendees. Sponsored by Shopify, Rails World City Host." - session.ends_at = Time.zone.local(2024, month, registration_day, 19, 0o0) - session.location = tbd_location - end Session.find_or_create_by!( conference: conference, - title: "WNB.rb pre-Rails World meetup, sponsored by Shopify", - starts_at: Time.zone.local(2024, month, registration_day, 16, 0) + title: "What to do tonight", + starts_at: Time.zone.local(2024, month, registration_day, 17, 0) ) do |session| - session.description = "A chance for women and non-binary Rails World attendees to meet up before Rails World. Hosted by WNB.rb and sponsored by Shopify." - session.ends_at = Time.zone.local(2024, month, registration_day, 19, 0o0) - session.location = shopify_port + session.description = "
\n
If you are looking for things to do in the city after you pick up your badge, check out this list of recommendations created for you by the Toronto Ruby meetup group.

Don't stay out too late - the keynote tomorrow morning waits for no one.
\n
\n" + session.ends_at = Time.zone.local(2024, month, registration_day, 18, 0o0) + session.location = toronto_location end Session.find_or_create_by!( @@ -361,14 +331,14 @@ namespace :db do title: "Doors Open", starts_at: Time.zone.local(2024, month, start_day, 9, 0) ) do |session| - session.description = "Rails World attendees are welcome to register, enter, and grab a coffee and light breakfast before the keynote begins." + session.description = "Rails World attendees are welcome to enter Evergreen Brickworks, pick up your swag bag, and grab a coffee and light breakfast before the keynote begins." session.ends_at = Time.zone.local(2024, month, start_day, 9, 45) session.location = sponsor_garden end Session.find_or_create_by!( conference: conference, - title: "Opening Keynote", + title: "Rails World Opening Keynote", starts_at: Time.zone.local(2024, month, start_day, 9, 45) ) do |session| session.description = "DHH will kick off the second edition of Rails World in Toronto with an Opening Keynote highlighting what is new in Rails today, and where the framework is headed tomorrow." @@ -404,10 +374,10 @@ namespace :db do Session.find_or_create_by!( conference: conference, - title: "What to do in the breaks", + title: "Lunch (and other things to do in the breaks)", starts_at: Time.zone.local(2024, month, start_day, 11, 45) ) do |session| - session.description = "During lunch and breaks between sessions on both days, attendees can:
  • Catch 10-15 min Community talks on the Lightning Track (sponsored by Shopify)
  • Chat with our sponsors in the Sponsor Garden
  • Hang out in the Shopify Lounge
  • Follow a workshop (sponsored by Coder)
  • Drop by the Test Double Office Hours for pair programming and career advice
  • Explore the historic venue
  • Take a walk in the nature park located behind the venue
  • Grab an espresso or latte from the coffee carts (sponsored by Cedarcode)
  • Or grab an item from the Food Trucks (sponsored by Shopify)
" + session.description = "
\n
Lunch will be served in the Sponsor Garden and in the Pavilion

During lunch and breaks between sessions on both days, attendees can also: 

  • Catch 5-10 min Community talks on the Lightning Track (sponsored by Shopify
  • Chat with our sponsors in the Sponsor Garden 
  • Hang out in the Shopify Lounge 
  • Sign up here for pair programming or career advice in the Double Up Lab (sponsored by Test Double)
  • Chill out in the kilns of this historic venue
  • Grab an espresso or latte from the coffee carts (sponsored by Cedarcode
  • Or grab an item from the food trucks (sponsored by Shopify) - Note that the food trucks will be open all day from 11 am to 4 pm, so drop by with your ticket for a free item whenever you like.
  • Go touch grass. (We are located in one of Toronto's most calming nature parks.)
\n
\n" session.ends_at = Time.zone.local(2024, month, start_day, 13, 0) session.location = break_location end @@ -472,6 +442,26 @@ namespace :db do session.tags = [security, rails_8] end + Session.find_or_create_by!( + conference: conference, + title: "Lightning Track", + starts_at: Time.zone.local(2024, month, start_day, 14, 15) + ) do |session| + session.description = "Coming soon: Community Talks taking place during this break will be listed here." + session.ends_at = Time.zone.local(2024, month, start_day, 14, 45) + session.location = lightning_track + end + + Session.find_or_create_by!( + conference: conference, + title: "Double Up Lab (sponsored by Test Double)", + starts_at: Time.zone.local(2024, month, start_day, 14, 15) + ) do |session| + session.description = "
\n" + session.ends_at = Time.zone.local(2024, month, start_day, 14, 45) + session.location = double_up_location + end + Session.find_or_create_by!( conference: conference, title: "An upgrade handbook to Rails 8", @@ -496,6 +486,26 @@ namespace :db do session.tags = [productivity, developer_experience] end + Session.find_or_create_by!( + conference: conference, + title: "Lightning Track", + starts_at: Time.zone.local(2024, month, start_day, 15, 15) + ) do |session| + session.description = "Coming soon: Community Talks taking place during this break will be listed here." + session.ends_at = Time.zone.local(2024, month, start_day, 15, 45) + session.location = lightning_track + end + + Session.find_or_create_by!( + conference: conference, + title: "Double Up Lab", + starts_at: Time.zone.local(2024, month, start_day, 15, 15) + ) do |session| + session.description = "
\n
Join Test Double in the Double Up Lab to pair with one of our double agents on Rails, no-strings-attached career advice, and more. Bring something to work on, check out what we've been up to, or just recharge and chat.

Book time here.

You can also stop by 1:00-2:15 pm daily to say hello, score some swag, schedule a session, or get resume & career advice.
\n
\n" + session.ends_at = Time.zone.local(2024, month, start_day, 15, 45) + session.location = double_up_location + end + Session.find_or_create_by!( conference: conference, title: "Frontiers of development productivity in Rails", @@ -525,39 +535,29 @@ namespace :db do title: "Matz & DHH Fireside chat, hosted by Tobias Lütke", starts_at: Time.zone.local(2024, month, start_day, 16, 30) ) do |session| - session.description = "We are pleased to welcome Ruby creator and special guest Yukihiro Matsumoto (Matz) to the Rails World stage for a fireside chat with Rails creator David Heinemeier Hansson (DHH). Together on stage for the very first time! Hosted by Shopify founder Tobias Lütke, this is sure to be a conversation you don’t want to miss." + session.description = "
\n
We are pleased to welcome Ruby creator and special guest Yukihiro Matsumoto (Matz) to the Rails World stage for a fireside chat with Rails creator David Heinemeier Hansson (DHH). Together on stage for the very first time!

Hosted by Shopify founder Tobias Lütke, this is sure to be a conversation you don’t want to miss.
\n
\n" session.ends_at = Time.zone.local(2024, month, start_day, 17, 30) session.location = track_1 session.speakers = [matz_speaker, david_heinemeier_hansson_speaker, tobias_luetke_speaker] session.tags = [community, insights] end - Session.find_or_create_by!( - conference: conference, - title: "Toronto Ruby drinks sponsored by Clio", - starts_at: Time.zone.local(2024, month, start_day, 19, 0) - ) do |session| - session.description = "Come hang out with the local Toronto Ruby meetup group. Grab a beer and have a bite, and recharge for Day 2.

This evening event wouldn’t be possible without the generous support and sponsorship of our Gold sponsor, Clio. Stop by their booth for a ticket to the event. Badges still required." - session.ends_at = Time.zone.local(2024, month, start_day, 22, 30) - session.location = tbd_attendees_location - end - Session.find_or_create_by!( conference: conference, title: "Doors Open", starts_at: Time.zone.local(2024, month, second_day, 9, 0) ) do |session| - session.description = "Rails World attendees are welcome to register, enter, and grab a coffee before the keynote begins." + session.description = "Rails World attendees are welcome to enter Evergreen Brickworks, pick up your swag bag, and grab a coffee and light breakfast before the keynote begins." session.ends_at = Time.zone.local(2024, month, second_day, 10, 0) session.location = sponsor_lounge end Session.find_or_create_by!( conference: conference, - title: "Opening Keynote", + title: "Keynote: The Myth of the Modular Monolith", starts_at: Time.zone.local(2024, month, second_day, 10, 0) ) do |session| - session.description = "More information coming soon." + session.description = "As Rails applications grow over time and turn into a so-called \"ball of mud\", organizations ask themselves what's next? Should we stay the course with a monolith or migrate to microservices? At Shopify we went down the path of modularizing our monolith and since then GitHub, Gusto, and others have followed our lead. But after 6 years it's time to ask ourselves: \"Did we fix what we set out to fix? Is this better than before?\"" session.ends_at = Time.zone.local(2024, month, second_day, 11, 0) session.location = track_1 session.speakers = [eileen_uchitelle_speaker] @@ -589,10 +589,10 @@ namespace :db do Session.find_or_create_by!( conference: conference, - title: "What to do in the breaks", + title: "Lunch (and other things to do in the breaks)", starts_at: Time.zone.local(2024, month, second_day, 11, 45) ) do |session| - session.description = "During lunch and breaks between sessions on both days, attendees can:
  • Catch 10-15 min Community talks on the Lightning Track (sponsored by Shopify)
  • Chat with our sponsors in the Sponsor Garden
  • Hang out in the Shopify Lounge
  • Follow a workshop (sponsored by Coder)
  • Drop by the Test Double Office Hours for pair programming and career advice
  • Explore the historic venue
  • Take a walk in the nature park located behind the venue
  • Grab an espresso or latte from the coffee carts (sponsored by Cedarcode)
  • Or grab an item from the Food Trucks (sponsored by Shopify)
" + session.description = "
\n
Lunch will be served in the Sponsor Garden and in the Pavilion

During lunch and breaks between sessions on both days, attendees can also: 

  • Catch 5-10 min Community talks on the Lightning Track (sponsored by Shopify
  • Chat with our sponsors in the Sponsor Garden 
  • Hang out in the Shopify Lounge 
  • Sign up here for pair programming or career advice in the Double Up Lab (sponsored by Test Double)
  • Chill out in the kilns of this historic venue
  • Grab an espresso or latte from the coffee carts (sponsored by Cedarcode
  • Or grab an item from the food trucks (sponsored by Shopify) - Note that the food trucks will be open all day from 11 am to 4 pm, so drop by with your ticket for a free item whenever you like.
  • Go touch grass. (We are located in one of Toronto's most calming nature parks.)
\n
\n" session.ends_at = Time.zone.local(2024, month, second_day, 13, 0) session.location = break_location end @@ -621,18 +621,6 @@ namespace :db do session.tags = [performance, storage_solutions] end - Session.find_or_create_by!( - conference: conference, - title: "Making accessible web apps with Rails and Hotwire", - starts_at: Time.zone.local(2024, month, second_day, 13, 45) - ) do |session| - session.description = "Nowadays, there is a lot of talk about accessibility, but is your web app accessible? In this session, I will share my perspective as a blind developer on how to build accessible web apps with real-world examples, beyond saying it’s important and the need to use ARIA. I will start by demonstrating how a blind person uses a screen reader to navigate the web, so that you can understand the most common challenges faced by blind people every day. This will help clarify several requirements that people currently spend too much time on. Instead, we can focus on the most relevant errors and aspects to bear in mind when developing and designing interfaces. We will cover the most common patterns with code examples, taking advantage of tools provided by Rails, Hotwire, and the browser." - session.ends_at = Time.zone.local(2024, month, second_day, 14, 15) - session.location = track_1 - session.speakers = [bruno_prieto_speaker] - session.tags = [hotwire] - end - Session.find_or_create_by!( conference: conference, title: "The Modern Programmer’s Guide to Neovim and Zellij", @@ -645,6 +633,18 @@ namespace :db do session.tags = [productivity, tools, developer_experience] end + Session.find_or_create_by!( + conference: conference, + title: "Making accessible web apps with Rails and Hotwire", + starts_at: Time.zone.local(2024, month, second_day, 13, 45) + ) do |session| + session.description = "Nowadays, there is a lot of talk about accessibility, but is your web app accessible? In this session, I will share my perspective as a blind developer on how to build accessible web apps with real-world examples, beyond saying it’s important and the need to use ARIA. I will start by demonstrating how a blind person uses a screen reader to navigate the web, so that you can understand the most common challenges faced by blind people every day. This will help clarify several requirements that people currently spend too much time on. Instead, we can focus on the most relevant errors and aspects to bear in mind when developing and designing interfaces. We will cover the most common patterns with code examples, taking advantage of tools provided by Rails, Hotwire, and the browser." + session.ends_at = Time.zone.local(2024, month, second_day, 14, 15) + session.location = track_1 + session.speakers = [bruno_prieto_speaker] + session.tags = [hotwire] + end + Session.find_or_create_by( conference: conference, title: "Prepare to tack - Steering Rails apps out of technical debt", @@ -657,6 +657,16 @@ namespace :db do session.tags = [performance, refactoring] end + Session.find_or_create_by!( + conference: conference, + title: "Double Up Lab (sponsored by Test Double)", + starts_at: Time.zone.local(2024, month, second_day, 14, 15) + ) do |session| + session.description = "
\n
Join Test Double in the Double Up Lab to pair with one of our double agents on Rails, no-strings-attached career advice, and more. Bring something to work on, check out what we've been up to, or just recharge and chat.

Book time here.

You can also stop by 1:00-2:15 pm daily to say hello, score some swag, schedule a session, or get resume & career advice.
\n
\n" + session.ends_at = Time.zone.local(2024, month, second_day, 14, 45) + session.location = double_up_location + end + Session.find_or_create_by( conference: conference, title: "The Rails Boot Process", @@ -681,6 +691,16 @@ namespace :db do session.tags = [integrations, testing, best_practices] end + Session.find_or_create_by!( + conference: conference, + title: "Double Up Lab", + starts_at: Time.zone.local(2024, month, second_day, 15, 15) + ) do |session| + session.description = "
\n
Join Test Double in the Double Up Lab to pair with one of our double agents on Rails, no-strings-attached career advice, and more. Bring something to work on, check out what we've been up to, or just recharge and chat.

Book time here.

You can also stop by 1:00-2:15 pm daily to say hello, score some swag, schedule a session, or get resume & career advice.
\n
\n" + session.ends_at = Time.zone.local(2024, month, second_day, 15, 45) + session.location = double_up_location + end + Session.find_or_create_by( conference: conference, title: "Empowering the Individual - Rails on AI", @@ -716,15 +736,5 @@ namespace :db do session.speakers = [aaron_patterson_speaker] session.tags = [community] end - - Session.find_or_create_by!( - conference: conference, - title: "Shopify Closing Party", - starts_at: Time.zone.local(2024, month, second_day, 19, 0) - ) do |session| - session.description = "Join us as we close out Rails World with a takeover at the Shopify Port. Three floors of music, games, food, drink, and fun, with plenty of space for pair programming if you have any code you need to tackle. Pre-registration will be required (attendees will be sent more details). Don’t forget your badge!" - session.ends_at = Time.zone.local(2024, month, second_day, 22, 0) - session.location = shopify_port - end end end diff --git a/public/404.html b/public/404.html index 2be3af26..4dade267 100644 --- a/public/404.html +++ b/public/404.html @@ -4,64 +4,133 @@ The page you were looking for doesn't exist (404) - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

+ + -

If you are the application owner check the logs for more information.

-
- + +
+
+
+ + + + + + +
+

404

+

+ We couldn't find... +
+ Well, you already know what this means. +

+ + Return to the homepage + +
+
+ diff --git a/public/406-unsupported-browser.html b/public/406-unsupported-browser.html index 7cf1e168..3e335337 100644 --- a/public/406-unsupported-browser.html +++ b/public/406-unsupported-browser.html @@ -4,63 +4,129 @@ Your browser is not supported (406) - - -
-
-

Your browser is not supported.

-

Please upgrade your browser to continue.

+ + -
- + +
+
+
+ + + + + +
+

406

+

+ Unsupported browser. +
+ Please upgrade your browser to continue. +

+ + Refresh the page + +
+
+ diff --git a/public/422.html b/public/422.html index c08eac0d..e423f60d 100644 --- a/public/422.html +++ b/public/422.html @@ -4,64 +4,130 @@ The change you wanted was rejected (422) - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

+ + -

If you are the application owner check the logs for more information.

-
- + +
+
+
+ + + + + +
+

422

+

+ Maybe you tried to change something +
+ you didn't have access to. +

+ + Return to the homepage + +
+
+ diff --git a/public/500.html b/public/500.html index 78a030af..f1a62854 100644 --- a/public/500.html +++ b/public/500.html @@ -4,63 +4,126 @@ We're sorry, but something went wrong (500) - - -
-
-

We're sorry, but something went wrong.

+ + -

If you are the application owner check the logs for more information.

-
- + +
+
+
+ + + + + + + +
+

500

+

+ Turn around and say hi to +
+ someone while we fix this. +

+ + Return to the homepage + +
+
+ diff --git a/spec/factories/sessions.rb b/spec/factories/sessions.rb index 426cfe3f..fc036a96 100644 --- a/spec/factories/sessions.rb +++ b/spec/factories/sessions.rb @@ -4,6 +4,7 @@ # # id :integer not null, primary key # ends_at :datetime not null +# public :boolean default(TRUE), not null # sent_reminders :json # slug :string # starts_at :datetime not null @@ -16,8 +17,10 @@ # Indexes # # index_sessions_on_conference_id (conference_id) +# index_sessions_on_ends_at (ends_at) # index_sessions_on_location_id (location_id) # index_sessions_on_slug (slug) UNIQUE +# index_sessions_on_starts_at (starts_at) # FactoryBot.define do factory :session do diff --git a/spec/models/session_spec.rb b/spec/models/session_spec.rb index a3232aec..2aa14f5c 100644 --- a/spec/models/session_spec.rb +++ b/spec/models/session_spec.rb @@ -4,6 +4,7 @@ # # id :integer not null, primary key # ends_at :datetime not null +# public :boolean default(TRUE), not null # sent_reminders :json # slug :string # starts_at :datetime not null @@ -16,8 +17,10 @@ # Indexes # # index_sessions_on_conference_id (conference_id) +# index_sessions_on_ends_at (ends_at) # index_sessions_on_location_id (location_id) # index_sessions_on_slug (slug) UNIQUE +# index_sessions_on_starts_at (starts_at) # require "rails_helper" diff --git a/vendor/javascript/@stimulus-components--password-visibility.js b/vendor/javascript/@stimulus-components--password-visibility.js new file mode 100644 index 00000000..649eaf92 --- /dev/null +++ b/vendor/javascript/@stimulus-components--password-visibility.js @@ -0,0 +1,2 @@ +import{Controller as s}from"@hotwired/stimulus";const t=class _PasswordVisibility extends s{connect(){this.hidden=this.inputTarget.type==="password",this.class=this.hasHiddenClass?this.hiddenClass:"hidden"}toggle(s){s.preventDefault(),this.inputTarget.type=this.hidden?"text":"password",this.hidden=!this.hidden,this.iconTargets.forEach((s=>s.classList.toggle(this.class)))}};t.targets=["input","icon"],t.classes=["hidden"];let i=t;export{i as default}; +