diff --git a/Gemfile b/Gemfile
index a7dff37..6ec0481 100644
--- a/Gemfile
+++ b/Gemfile
@@ -17,21 +17,10 @@ gem 'sass-rails', '>= 6'
gem 'webpacker', '~> 5.0'
# Turbolinks makes navigating your web appliletter_openercation faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
-# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
-gem 'redis', '~> 4.0'
-# Use Active Model has_secure_password
-# gem 'bcrypt', '~> 3.1.7'
-
-# Use Active Storage variant
-# gem 'image_processing', '~> 1.2'
-gem 'letter_opener', group: :development
-
-# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.4', require: false
+gem 'redis', '~> 4.0'
-gem 'bootstrap'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: %i[mri mingw x64_mingw]
@@ -39,15 +28,16 @@ end
group :development do
gem 'bullet', '~> 7.0'
-
+ gem 'delayed_job_web', '~> 1.4.4', require: 'delayed_job_active_record'
+ gem 'erb_lint', '~> 0.4.0'
+ gem 'htmlbeautifier', '~> 1.4'
+ gem 'letter_opener'
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 4.1.0'
# Display performance information such as SQL time and flame graphs for each request in your browser.
# Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
gem 'listen', '~> 3.3'
gem 'rack-mini-profiler', '~> 2.0'
- # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
- # gem 'spring'
end
group :test do
@@ -62,6 +52,7 @@ end
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
gem 'activestorage', '~> 6.1'
+
gem 'devise', '~> 4.9'
gem 'tailwindcss', '~> 0.1.1'
@@ -70,28 +61,16 @@ gem 'tailwindcss-rails', '~> 2.0'
gem 'devise-two-factor', '~> 4.0'
-gem 'dotenv-rails'
-gem 'rqrcode', '~> 2.1'
-
gem 'stimulus-rails'
gem 'hotwire-rails', '~> 0.1.3'
-# gem "autoprefixer"
-
gem 'kaminari'
-gem 'whenever', '~> 1.0'
gem 'cancancan', '~> 3.5'
-gem 'attr_encrypted', '~> 3.1'
-
gem 'daemons'
+
gem 'delayed_job_active_record'
-gem 'delayed_job_web', '~> 1.4.4', require: 'delayed_job_active_record'
gem 'twilio-ruby', '~> 5.77'
-
-gem 'erb_lint', '~> 0.4.0'
-
-gem 'htmlbeautifier', '~> 1.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0af0d81..2b08291 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -63,10 +63,8 @@ GEM
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
- attr_encrypted (3.1.0)
+ attr_encrypted (4.0.0)
encryptor (~> 3.0.0)
- autoprefixer-rails (10.4.13.0)
- execjs (~> 2)
bcrypt (3.1.18)
better_html (2.0.1)
actionview (>= 6.0)
@@ -78,17 +76,13 @@ GEM
bindex (0.8.1)
bootsnap (1.16.0)
msgpack (~> 1.2)
- bootstrap (5.2.3)
- autoprefixer-rails (>= 9.1.0)
- popper_js (>= 2.11.6, < 3)
- sassc-rails (>= 2.0.0)
builder (3.2.4)
bullet (7.0.7)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
cancancan (3.5.0)
- capybara (3.39.1)
+ capybara (3.39.2)
addressable
matrix
mini_mime (>= 0.1.3)
@@ -97,8 +91,6 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
- chronic (0.10.2)
- chunky_png (1.4.0)
concurrent-ruby (1.2.2)
crass (1.0.6)
daemons (1.4.1)
@@ -125,10 +117,6 @@ GEM
devise (~> 4.0)
railties (< 7.1)
rotp (~> 6.0)
- dotenv (2.8.1)
- dotenv-rails (2.8.1)
- dotenv (= 2.8.1)
- railties (>= 3.2)
encryptor (3.0.0)
erb_lint (0.4.0)
activesupport
@@ -138,8 +126,7 @@ GEM
rubocop
smart_properties
erubi (1.12.0)
- execjs (2.8.1)
- faraday (2.7.5)
+ faraday (2.7.6)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
@@ -151,13 +138,10 @@ GEM
stimulus-rails
turbo-rails
htmlbeautifier (1.4.2)
- i18n (1.13.0)
+ i18n (1.14.1)
concurrent-ruby (~> 1.0)
- jbuilder (2.11.5)
- actionview (>= 5.0.0)
- activesupport (>= 5.0.0)
json (2.6.3)
- jwt (2.7.0)
+ jwt (2.7.1)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
@@ -193,7 +177,7 @@ GEM
msgpack (1.7.1)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
- net-imap (0.3.4)
+ net-imap (0.3.6)
date
net-protocol
net-pop (0.1.2)
@@ -209,14 +193,14 @@ GEM
racc (~> 1.4)
orm_adapter (0.5.0)
parallel (1.23.0)
- parser (3.2.2.1)
+ parser (3.2.2.3)
ast (~> 2.4.1)
+ racc
pg (1.5.3)
- popper_js (2.11.7)
public_suffix (5.0.1)
puma (5.6.5)
nio4r (~> 2.0)
- racc (1.6.2)
+ racc (1.7.0)
rack (2.2.7)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
@@ -244,8 +228,9 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
- rails-html-sanitizer (1.5.0)
- loofah (~> 2.19, >= 2.19.1)
+ rails-html-sanitizer (1.6.0)
+ loofah (~> 2.21)
+ nokogiri (~> 1.14)
railties (6.1.7.3)
actionpack (= 6.1.7.3)
activesupport (= 6.1.7.3)
@@ -258,27 +243,23 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.8.1)
- regexp_parser (2.8.0)
+ regexp_parser (2.8.1)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.5)
rotp (6.2.2)
- rqrcode (2.2.0)
- chunky_png (~> 1.0)
- rqrcode_core (~> 1.0)
- rqrcode_core (1.2.0)
- rubocop (1.51.0)
+ rubocop (1.52.1)
json (~> 2.3)
parallel (~> 1.10)
- parser (>= 3.2.0.0)
+ parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
- rubocop-ast (1.28.1)
+ rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
@@ -293,7 +274,7 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
- selenium-webdriver (4.9.1)
+ selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@@ -319,7 +300,7 @@ GEM
tailwindcss-rails (2.0.29-x86_64-linux)
railties (>= 6.0.0)
thor (1.2.2)
- tilt (2.1.0)
+ tilt (2.2.0)
timeout (0.3.2)
turbo-rails (1.4.0)
actionpack (>= 6.0.0)
@@ -356,8 +337,6 @@ GEM
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
- whenever (1.0.0)
- chronic (>= 0.6.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.8)
@@ -368,9 +347,7 @@ PLATFORMS
DEPENDENCIES
activestorage (~> 6.1)
- attr_encrypted (~> 3.1)
bootsnap (>= 1.4.4)
- bootstrap
bullet (~> 7.0)
byebug
cancancan (~> 3.5)
@@ -380,11 +357,9 @@ DEPENDENCIES
delayed_job_web (~> 1.4.4)
devise (~> 4.9)
devise-two-factor (~> 4.0)
- dotenv-rails
erb_lint (~> 0.4.0)
hotwire-rails (~> 0.1.3)
htmlbeautifier (~> 1.4)
- jbuilder (~> 2.7)
kaminari
letter_opener
listen (~> 3.3)
@@ -393,7 +368,6 @@ DEPENDENCIES
rack-mini-profiler (~> 2.0)
rails (~> 6.1.7, >= 6.1.7.2)
redis (~> 4.0)
- rqrcode (~> 2.1)
sass-rails (>= 6)
selenium-webdriver (>= 4.0.0.rc1)
stimulus-rails
@@ -405,7 +379,6 @@ DEPENDENCIES
web-console (>= 4.1.0)
webdrivers
webpacker (~> 5.0)
- whenever (~> 1.0)
RUBY VERSION
ruby 3.1.3p185
diff --git a/app/channels/match_channel.rb b/app/channels/match_channel.rb
new file mode 100644
index 0000000..7f9af39
--- /dev/null
+++ b/app/channels/match_channel.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# channel for timer and selection
+class MatchChannel < ApplicationCable::Channel
+ def subscribed
+ stream_from "match_channel_#{params[:match_id]}"
+ end
+end
diff --git a/app/channels/timer_channel.rb b/app/channels/timer_channel.rb
deleted file mode 100644
index 84df354..0000000
--- a/app/channels/timer_channel.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-# channel for timer and selection
-class TimerChannel < ApplicationCable::Channel
- def subscribed
- stream_from "timer_channel_#{params[:match_id]}"
- end
-end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e55c247..1060670 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,14 +3,15 @@
class ApplicationController < ActionController::Base
protect_from_forgery prepend: true
before_action :configure_permitted_parameters, if: :devise_controller?
- rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from CanCan::AccessDenied, with: :access_denied
+ rescue_from ActionController::RoutingError, with: :not_found
def access_denied
render file: "#{Rails.root}/public/403.html", formats: [:html], status: 403, layout: false
end
- def record_not_found
+ def not_found
render file: "#{Rails.root}/public/404.html", formats: [:html], status: 404, layout: false
end
@@ -26,7 +27,7 @@ def after_resetting_password_path_for(user)
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: %i[name email password phone_number])
- devise_parameter_sanitizer.permit(:account_update, keys: %i[name email password phone_number avatar])
+ devise_parameter_sanitizer.permit(:account_update, keys: %i[name email password phone_number image])
devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
end
end
diff --git a/app/controllers/leaderboard_controller.rb b/app/controllers/leaderboard_controller.rb
index 0223eaa..0dac98e 100644
--- a/app/controllers/leaderboard_controller.rb
+++ b/app/controllers/leaderboard_controller.rb
@@ -1,6 +1,6 @@
# LeaderBoard Controller
class LeaderboardController < ApplicationController
def index
- @players = LeaderBoard.new.result
+ @players = LeaderBoardCreator.call
end
end
diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb
index 88aa8f4..6670e1a 100644
--- a/app/controllers/matches_controller.rb
+++ b/app/controllers/matches_controller.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# Matches_Controller
class MatchesController < ApplicationController
load_and_authorize_resource :tournament
load_and_authorize_resource through: :tournament
@@ -16,31 +15,19 @@ def all
def show; end
- def new
- @match = Match.new
- end
-
def playmatch
- @match = Match.includes([:users]).find(params[:match_id])
- redirect_to result_tournament_match_path(match_id: @match) unless @match.match_winner_id.nil?
- @players = @match.users
- @remaining_tries = @match.remaining_tries(@players.first.id)
- @done_tries = @match.selections.by_user(@players.first.id).size
- @is_player = @match.users.include?(current_user)
@messages = @match.messages.includes([:user]).all.reverse
- end
+ return if @match.done?
- def result
- @is_player = @match.users.include?(current_user)
- @players = @match.users
- @players_selections = @match.selections.order(:try_num).group_by(&:user_id)
- @players_scores = @match.selections.group(:user_id).winner.count
- @result_message = @match.result_message(current_user.id)
+ @players_data = @match.players_data_by_current_user(current_user)
end
- private
+ def result
+ return if @match.undone?
- def match_params
- params.require(:match).permit(:match_winner_id, :winner_score, :match_time, :tournament_id)
+ @players_data = @match.players_data_by_current_user(current_user)
+ @players_selections = @match.selections.includes(:user).order(:try_num).group_by(&:user_id)
+ @players_scores = @match.selections.winner.group(:user_id).count
+ @result_message = @match.result_message(current_user.id)
end
end
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
index 641887d..d413533 100644
--- a/app/controllers/messages_controller.rb
+++ b/app/controllers/messages_controller.rb
@@ -2,26 +2,20 @@
class MessagesController < ApplicationController
load_and_authorize_resource
- def index; end
-
- def new
- @message = Message.new
- end
def create
@message = Message.new(message_params)
- respond_to do |format|
- if @message.save
- format.json { render json: { data: 'Saved' }, status: :ok }
- else
- format.json { render json: { errors: @message.errors.full_messages }, status: :unprocessable_entity }
- end
+ @message.user_id = current_user.id
+ if @message.save
+ flash[:notice] = 'Message Sent'
+ else
+ flash[:error] = @message.errors.full_messages.join(', ')
end
end
private
def message_params
- params.permit(:user_id, :match_id, :message)
+ params.permit(:match_id, :content)
end
end
diff --git a/app/controllers/selection_controller.rb b/app/controllers/selection_controller.rb
index a558ca6..5d890e7 100644
--- a/app/controllers/selection_controller.rb
+++ b/app/controllers/selection_controller.rb
@@ -1,26 +1,21 @@
# frozen_string_literal: true
class SelectionController < ApplicationController
+ load_and_authorize_resource
before_action :authenticate_user!
+
def create
@selection = Selection.new(selection_params)
- @selection.add_try_num
- respond_to do |format|
- if @selection.save
- format.json { render json: { data: 'Saved' }, status: :ok }
- else
- format.json { render json: { errors: @selection.errors.full_messages }, status: :unprocessable_entity }
- end
+ if @selection.save
+ flash[:notice] = 'Choice Saved'
+ else
+ flash[:error] = @selection.errors.full_messages.join(', ')
end
end
- def new
- @selection = Selection.new
- end
-
private
def selection_params
- params.require(:selection).permit(:match_id, :user_id, :selection)
+ params.require(:selection).permit(:match_id, :user_id, :choice)
end
end
diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb
index d50a962..49058d4 100644
--- a/app/controllers/tournaments_controller.rb
+++ b/app/controllers/tournaments_controller.rb
@@ -1,73 +1,56 @@
-# Tournamnets Controller
+# frozen_string_literal: true
+
class TournamentsController < ApplicationController
load_and_authorize_resource
before_action :authenticate_user!, except: [:index]
def index
- @tournaments = Tournament.includes(:users).all
- @tournaments = @tournaments.order(:registration_deadline).page(params[:page])
+ @tournaments = @tournaments.includes(%i[users winner]).desc.page(params[:page])
end
def show; end
- def new
- @tournament = Tournament.new
- end
+ def new; end
def edit; end
- def register
- @tournaments_user = TournamentsUser.new(user: current_user, tournament: @tournament)
- if @tournaments_user.save
- redirect_to tournament_url(@tournament), notice: 'You have registered for the tournament!'
- else
- redirect_to tournament_url(@tournament), notice: 'Already Registered'
- end
- end
-
def create_matches
- registered_users = @tournament.users
- length = registered_users.length
- return unless (length - 8) != 0
-
begin
- MatchCreator.new(@tournament, registered_users).create_match
- redirect_to tournament_path(@tournament), notice: 'Matches Generated'
+ TournamentMatchesCreator.new(@tournament, @tournament.users).call
+ flash[:notice] = 'Matches Generated.'
rescue StandardError => e
- redirect_to tournament_path(@tournament), alert: "Error generating matches: #{e.message}"
+ flash[:alert] = "Error generating matches: #{e.message}"
end
+ redirect_to tournament_path(@tournament)
end
def create
- @tournament = Tournament.new(tournament_params)
-
- respond_to do |format|
- if @tournament.save
- format.html { redirect_to tournament_url(@tournament), notice: 'Tournament was successfully created.' }
- else
- format.html { render :new, status: :unprocessable_entity }
- end
+ if @tournament.save
+ flash[:notice] = 'Tournament was successfully created.'
+ redirect_to tournament_path(@tournament)
+ else
+ flash[:errors] = @tournament.errors.full_messages.join(', ')
+ render :new
end
end
def update
- respond_to do |format|
- if @tournament.update(tournament_params)
- format.html { redirect_to tournament_url(@tournament), notice: 'Tournament was successfully updated.' }
- else
- format.html { render :edit, status: :unprocessable_entity }
- end
+ if @tournament.update(tournament_params)
+ flash[:notice] = 'Tournament was successfully updated.'
+ redirect_to tournament_path(@tournament)
+ else
+ flash[:errors] = @tournament.errors.full_messages.join(', ')
+ render :edit
end
end
def destroy
- respond_to do |format|
- if @tournament.destroy
- format.html { redirect_to tournaments_url, notice: 'Tournament was successfully deleted.' }
- else
- format.html { render :edit, status: :unprocessable_entity }
- end
+ if @tournament.destroy
+ flash[:notice] = 'Tournament was successfully deleted.'
+ else
+ flash[:errors] = @tournament.errors.full_messages.join(', ')
end
+ redirect_to tournaments_path
end
private
@@ -77,7 +60,7 @@ def tournament_params
:description,
:start_date,
:end_date,
- :tournament_winner_id,
+ :winner_id,
:image,
:registration_deadline)
end
diff --git a/app/controllers/tournaments_users_controller.rb b/app/controllers/tournaments_users_controller.rb
new file mode 100644
index 0000000..06c1158
--- /dev/null
+++ b/app/controllers/tournaments_users_controller.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class TournamentsUsersController < ApplicationController
+ before_action :authenticate_user!
+
+ def create
+ @tournament_user = TournamentsUser.new(tournament_user_params)
+ if @tournament_user.save
+ flash[:notice] = 'You have registered for the tournament!'
+ else
+ flash[:alert] = @tournament_user.errors.full_messages.join(', ')
+ end
+ redirect_to tournament_path(params[:id])
+ end
+
+ private
+
+ def tournament_user_params
+ { tournament_id: params[:id].to_i, user_id: current_user.id }
+ end
+end
diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb
index 90e7ff0..c1c81d6 100644
--- a/app/controllers/users/confirmations_controller.rb
+++ b/app/controllers/users/confirmations_controller.rb
@@ -15,7 +15,8 @@ def show
end
end
- # protected
+ protected
+
def after_confirmation_path_for(_resource_name, resource)
sign_in(resource)
root_path
diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb
index 71271bc..66df3b6 100644
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -27,9 +27,4 @@ def create
def after_sign_up_path_for(_resource)
root_path
end
-
- def configure_permitted_parameters
- devise_parameter_sanitizer.permit(:sign_up, keys: %i[avatar name phone_number])
- devise_parameter_sanitizer.permit(:account_update, keys: %i[avatar name phone_number])
- end
end
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index cf3a3da..a0d13d4 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -2,57 +2,31 @@
module Users
class SessionsController < Devise::SessionsController
- before_action :authenticate_2fa!, only: [:create]
+ before_action :generate_and_send_otp, only: [:create]
before_action :authenticate_user!, except: %i[new create destroy]
- before_action :load_and_authorize_resource, except: %i[new create destroy]
-
- def create
- super do |resource|
- if resource.valid? && resource.persisted?
- resource.update(
- otp_required_for_login: true
- )
- end
+ def verify_otp
+ user = User.find_by(id: session[:user_id])
+ if User.auth_with_2fa(user_params[:otp_attempt], user)
+ sign_in(:user, user)
+ redirect_to root_path, notice: 'OTP consumed Successfully.'
+ else
+ redirect_to new_user_session_path, alert: 'Invalid OTP code entered.'
end
end
private
- def authenticate_2fa!
- user = self.resource = find_user
+ def generate_and_send_otp
+ user = self.resource = User.find_by(email: user_params[:email])
+
return unless user
- if otp_attempt_present?
- authenticate_with_2fa(user)
- sign_in(:user, user)
- elsif valid_password_and_otp_required?(user)
+ if user.valid_password?(user_params[:password])
session[:user_id] = user.id
- send_otp_code(user)
+ TwoFactorAuthenticator.new(user).call
render 'user_otp/two_fa'
- end
- end
-
- def otp_attempt_present?
- user_params[:otp_attempt].present?
- end
-
- def authenticate_with_2fa(user)
- User.auth_with_2fa(user_params[:otp_attempt], user)
- end
-
- def valid_password_and_otp_required?(user)
- user.valid_password?(user_params[:password]) && user.otp_required_for_login
- end
-
- def send_otp_code(user)
- TwoFactorAuth.new(user).send_otp_code
- end
-
- def find_user
- if session[:user_id]
- User.find_by(id: session[:user_id])
- elsif user_params[:email]
- User.find_by(email: user_params[:email])
+ else
+ redirect_to new_user_session_path, alert: 'Invalid Email or Password.'
end
end
diff --git a/app/javascript/channels/match_channel.js b/app/javascript/channels/match_channel.js
new file mode 100644
index 0000000..4b22322
--- /dev/null
+++ b/app/javascript/channels/match_channel.js
@@ -0,0 +1,67 @@
+import consumer from "./consumer";
+import {
+ eventListenerToButtons,
+ disableButtons,
+} from "../packs/buttons_activity";
+import { selectionPost } from "../packs/selection_post";
+
+document.addEventListener("turbolinks:load", () => {
+ const matchId = document
+ .getElementById("match_id")
+ .getAttribute("data-match-id");
+ consumer.subscriptions.create(
+ { channel: "MatchChannel", match_id: matchId },
+ {
+ received(data) {
+ const player1Id = document
+ .getElementById("player1_id")
+ .getAttribute("data-player1-id");
+ const player2Id = document
+ .getElementById("player2_id")
+ .getAttribute("data-player2-id");
+ const userId = document
+ .getElementById("user_id")
+ .getAttribute("data-user-id");
+ if (data.user1_id != undefined) {
+ var choice1 = data.choice1;
+ var choice2 = data.choice2;
+ var status = data.status;
+ const user1ChoiceElement = document.getElementById(data.user1_id);
+ const user2ChoiceElement = document.getElementById(data.user2_id);
+ const displayDiv = document.getElementById("second_timer");
+ user1ChoiceElement.src = "/assets/" + choice1 + ".png";
+ user2ChoiceElement.src = "/assets/" + choice2 + ".png";
+ displayDiv.textContent = status;
+ if (data.done != undefined) {
+ setTimeout(function () {
+ location.reload(true);
+ }, 2000);
+ return;
+ }
+ } else {
+ var seconds = data.seconds;
+ var tryNum = data.try_num;
+ var tries = data.tries;
+ const displayDiv = document.getElementById("second_timer");
+ const triesDiv = document.getElementById(`tries_${matchId}`);
+ displayDiv.textContent = seconds;
+ if (seconds != 0) {
+ triesDiv.textContent = `Try ${tryNum} of ${tries}`;
+ if (seconds === 5) {
+ eventListenerToButtons();
+ const user1ChoiceElement = document.getElementById(player1Id);
+ const user2ChoiceElement = document.getElementById(player2Id);
+ user1ChoiceElement.src = "/assets/question.png";
+ user2ChoiceElement.src = "/assets/question.png";
+ }
+ } else {
+ disableButtons();
+ if ([player1Id, player2Id].includes(userId)) {
+ selectionPost();
+ }
+ }
+ }
+ },
+ }
+ );
+});
diff --git a/app/javascript/channels/room_channel.js b/app/javascript/channels/room_channel.js
index 9a4c390..97db491 100644
--- a/app/javascript/channels/room_channel.js
+++ b/app/javascript/channels/room_channel.js
@@ -12,48 +12,44 @@ document.addEventListener("turbolinks:load", () => {
received(data) {
const msgs = document.getElementById("message-list");
- const current_user = document.getElementById("current_user");
- const current_user_name = current_user.getAttribute(
- "data-current-user-name"
- );
- const msg_element = document.getElementById(
- `msg_field_${data.user_name}`
- );
+ const user_id = document
+ .getElementById("user_id")
+ .getAttribute("data-user-id");
+ const msg_element = document.getElementById(`msg_field_${user_id}`);
+ const current_user_name = document
+ .getElementById("current_user")
+ .getAttribute("data-current-user-name");
let messageHTML;
- if (data.user_name === current_user_name) {
- messageHTML = `
-
- ${data.user_name.substring(0, 3)}
-
-
-
- ${data.message}
-
-
-
-
`;
- } else {
- messageHTML = `
-
- ${data.user_name.substring(0, 3)}
-
-
-
- ${data.message}
-
-
-
-
`;
- }
-
+ messageHTML = `
+
+ ${data.sender_name.substring(0, 3)}
+
+
+
+ ${data.message}
+
+
+
+
`;
msgs.insertAdjacentHTML("afterbegin", messageHTML);
const last_elem = msgs.firstChild;
last_elem.scrollIntoView({ behavior: "smooth", block: "end" });
- if (msg_element) {
+ if (user_id == data.user_id) {
msg_element.value = "";
}
},
diff --git a/app/javascript/channels/timer_channel.js b/app/javascript/channels/timer_channel.js
deleted file mode 100644
index e74b80d..0000000
--- a/app/javascript/channels/timer_channel.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import consumer from "./consumer";
-import {
- event_listener_to_buttons,
- disable_buttons,
-} from "../packs/buttons_activity";
-import { try_post } from "../packs/try_post";
-
-document.addEventListener("turbolinks:load", () => {
- const element = document.getElementById("match_id");
- const user1Element = document.getElementById("player1_id");
- const user2Element = document.getElementById("player2_id");
- const playerElement = document.getElementById("player_check");
- const match_id = element.getAttribute("data-match-id");
- const player1_id = user1Element.getAttribute("data-player1-id");
- const player2_id = user2Element.getAttribute("data-player2-id");
- const is_player = playerElement.getAttribute("data-player-check");
- consumer.subscriptions.create(
- { channel: "TimerChannel", match_id: match_id },
- {
- connected() {},
-
- disconnected() {},
-
- received(data) {
- if (data.user1_id != undefined) {
- var selection1 = data.selection1;
- var selection2 = data.selection2;
- var status = data.status;
- const user1_selection = document.getElementById(player1_id);
- const user2_selection = document.getElementById(player2_id);
- const displayDiv = document.getElementById("second_timer");
- user1_selection.src = "/assets/" + selection1 + ".png";
- user2_selection.src = "/assets/" + selection2 + ".png";
- displayDiv.textContent = status;
- } else {
- var seconds = data.seconds;
- var try_num = data.try_num;
- var tries = data.tries;
- const displayDiv = document.getElementById("second_timer");
- const triesDiv = document.getElementById(`tries_${match_id}`);
- displayDiv.textContent = seconds;
- if (seconds != 0) {
- triesDiv.textContent = `Try ${try_num} of ${tries}`;
- if (seconds === 5) {
- event_listener_to_buttons();
- const user_selection = document.getElementById(player1_id);
- const opponent_selection = document.getElementById(player2_id);
- user_selection.src = "/assets/question.png";
- opponent_selection.src = "/assets/question.png";
- }
- } else {
- disable_buttons();
- if (is_player) {
- try_post();
- }
- }
- }
- },
- }
- );
-});
diff --git a/app/javascript/controllers/countdown_controller.js b/app/javascript/controllers/countdown_controller.js
index 9f69b68..24a282f 100644
--- a/app/javascript/controllers/countdown_controller.js
+++ b/app/javascript/controllers/countdown_controller.js
@@ -1,49 +1,25 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
- connect = (format = "seconds") => {
- const reg_date = this.data.get("time");
- const tournament_id = this.data.get("id");
- const number = reg_date;
+ connect() {
+ const timeInSeconds = this.data.get("time");
const daysElement = document.getElementById("days");
const hoursElement = document.getElementById("hours");
const minutesElement = document.getElementById("minutes");
const secondsElement = document.getElementById("seconds");
let countdown;
- convertFormat(format);
- function convertFormat(format) {
- switch (format) {
- case "seconds":
- return timer(number);
- case "minutes":
- return timer(number * 60);
- case "hours":
- return timer(number * 60 * 60);
- case "days":
- return timer(number * 60 * 60 * 24);
- }
- }
-
- function timer(seconds) {
- const now = Date.now();
- const then = now + seconds * 1000;
-
+ return countdownRefreshTimer(timeInSeconds);
+ function countdownRefreshTimer(seconds) {
+ const then = Date.now() + seconds * 1000;
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000);
-
if (secondsLeft <= 0) {
- document.getElementById("days").style.display = "none";
- document.getElementById("hours").style.display = "none";
- document.getElementById("minutes").style.display = "none";
- document.getElementById("seconds").style.display = "none";
- clearInterval(countdown);
+ location.reload(true);
return;
}
-
displayTimeLeft(secondsLeft);
}, 1000);
}
-
function displayTimeLeft(seconds) {
daysElement.textContent = Math.floor(seconds / 86400);
hoursElement.textContent = Math.floor((seconds % 86400) / 3600);
@@ -51,5 +27,5 @@ export default class extends Controller {
secondsElement.textContent =
seconds % 60 < 10 ? `0${seconds % 60}` : seconds % 60;
}
- };
+ }
}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index 8b8c589..ca6bc48 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -8,5 +8,5 @@ application.load(definitionsFromContext(context));
import CountdownController from "./countdown_controller";
application.register("countdown", CountdownController);
-import LoadController from "./load_controller"
-application.register("load", LoadController)
\ No newline at end of file
+import LoadController from "./load_controller";
+application.register("load", LoadController);
diff --git a/app/javascript/controllers/load_controller.js b/app/javascript/controllers/load_controller.js
index 999438c..2516e28 100644
--- a/app/javascript/controllers/load_controller.js
+++ b/app/javascript/controllers/load_controller.js
@@ -1,6 +1,5 @@
-import { Controller } from "@hotwired/stimulus"
+import { Controller } from "@hotwired/stimulus";
-// Connects to data-controller="laod-file"
export default class extends Controller {
connect() {
var loadFile = function (event) {
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 7582811..4fdcb7b 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -2,11 +2,11 @@ import Rails from "@rails/ujs";
import Turbolinks from "turbolinks";
import * as ActiveStorage from "@rails/activestorage";
import "./buttons_activity";
-import "./try_post";
+import "./selection_post";
import "channels";
import "../css/application.scss";
import "../controllers/countdown_controller";
-import "../packs/navigation.js";
+import "./navigation.js";
import "./flash.js";
Rails.start();
diff --git a/app/javascript/packs/buttons_activity.js b/app/javascript/packs/buttons_activity.js
index 01f28d3..4887017 100644
--- a/app/javascript/packs/buttons_activity.js
+++ b/app/javascript/packs/buttons_activity.js
@@ -1,61 +1,51 @@
-function event_listener_to_buttons() {
+function eventListenerToButtons() {
const user1Element = document.getElementById("player1_id");
const user2Element = document.getElementById("player2_id");
- const user = document.getElementById("user_id");
+ const currentUserElement = document.getElementById("user_id");
- const player1_id = user1Element.getAttribute("data-player1-id");
- const player2_id = user2Element.getAttribute("data-player2-id");
- const user_id = user.getAttribute("data-user-id");
- var user_image;
- var flag = false;
+ const player1Id = user1Element.getAttribute("data-player1-id");
+ const player2Id = user2Element.getAttribute("data-player2-id");
+ const currentUserId = currentUserElement.getAttribute("data-user-id");
- if (player1_id === user_id) {
- user_image = document.getElementById(player1_id);
- flag = true;
- } else if (player2_id === user_id) {
- user_image = document.getElementById(player2_id);
- flag = true;
- }
- if (flag) {
+ if ([player1Id, player2Id].includes(currentUserId)) {
+ var userImageElement = document.getElementById(currentUserId);
document
.getElementById("rock_button")
.addEventListener("click", function () {
- user_image.src = "/assets/rock.png";
+ userImageElement.src = "/assets/rock.png";
});
document
.getElementById("paper_button")
.addEventListener("click", function () {
- user_image.src = "/assets/paper.png";
+ userImageElement.src = "/assets/paper.png";
});
document
.getElementById("scissor_button")
.addEventListener("click", function () {
- user_image.src = "/assets/scissor.png";
+ userImageElement.src = "/assets/scissor.png";
});
- enable_button("rock_button");
- enable_button("paper_button");
- enable_button("scissor_button");
+ enableButtons();
}
}
-function disable_buttons() {
- disable_button("rock_button");
- disable_button("paper_button");
- disable_button("scissor_button");
+function disableButtons() {
+ disableButton("rock_button");
+ disableButton("paper_button");
+ disableButton("scissor_button");
}
-function enable_buttons() {
- enable_button("rock_button");
- enable_button("paper_button");
- enable_button("scissor_button");
+function enableButtons() {
+ enableButton("rock_button");
+ enableButton("paper_button");
+ enableButton("scissor_button");
}
-function disable_button(id) {
+function disableButton(id) {
document.getElementById(id).disabled = true;
document.getElementById(id).classList.remove("choice-button");
document.getElementById(id).classList.add("choice-button2");
}
-function enable_button(id) {
+function enableButton(id) {
document.getElementById(id).disabled = false;
document.getElementById(id).classList.remove("choice-button");
document.getElementById(id).classList.add("choice-button2");
}
-export { event_listener_to_buttons, disable_buttons };
+export { eventListenerToButtons, disableButtons };
diff --git a/app/javascript/packs/selection_post.js b/app/javascript/packs/selection_post.js
new file mode 100644
index 0000000..40be531
--- /dev/null
+++ b/app/javascript/packs/selection_post.js
@@ -0,0 +1,39 @@
+import Rails from "@rails/ujs";
+function selectionPost() {
+ const user1Element = document.getElementById("player1_id");
+ const user2Element = document.getElementById("player2_id");
+ const currentUserElement = document.getElementById("user_id");
+
+ const player1Id = user1Element.getAttribute("data-player1-id");
+ const player2Id = user2Element.getAttribute("data-player2-id");
+ const currentUserId = currentUserElement.getAttribute("data-user-id");
+ const matchElement = document.getElementById("match_id");
+ const tournamentElement = document.getElementById("tournament_id");
+ const matchId = matchElement.getAttribute("data-match-id");
+ const tournamentId = tournamentElement.getAttribute("data-tournament-id");
+ if ([player1Id, player2Id].includes(currentUserId)) {
+ const imageElement = document.getElementById(currentUserId);
+ var src = imageElement.src;
+ var fileName = src.substring(src.lastIndexOf("/") + 1);
+ const userSelectedChoice = fileName.substring(0, fileName.lastIndexOf("."));
+ if (fileName[0] == "q") {
+ return;
+ }
+ var data = {
+ match_id: matchId,
+ user_id: currentUserId,
+ choice: userSelectedChoice,
+ };
+ Rails.ajax({
+ url: `/tournaments/${tournamentId}/matches/${matchId}/selection`,
+ type: "post",
+ beforeSend(xhr, options) {
+ xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
+ options.data = JSON.stringify(data);
+ return true;
+ },
+ });
+ }
+}
+
+export { selectionPost };
diff --git a/app/javascript/packs/try_post.js b/app/javascript/packs/try_post.js
deleted file mode 100644
index ffed2d1..0000000
--- a/app/javascript/packs/try_post.js
+++ /dev/null
@@ -1,54 +0,0 @@
-function try_post() {
- const user1Element = document.getElementById("player1_id");
- const user2Element = document.getElementById("player2_id");
- const user = document.getElementById("user_id");
-
- const player1_id = user1Element.getAttribute("data-player1-id");
- const player2_id = user2Element.getAttribute("data-player2-id");
- const user_id = user.getAttribute("data-user-id");
- const match_element = document.getElementById("match_id");
- const match_id = match_element.getAttribute("data-match-id");
- let imageElement;
- if (player1_id == user_id) {
- imageElement = document.getElementById(player1_id);
- } else if (player2_id == user_id) {
- imageElement = document.getElementById(player2_id);
- }
- var src = imageElement.src;
- var fileName = src.substring(src.lastIndexOf("/") + 1);
- const user_selection_image = fileName.substring(0, fileName.lastIndexOf("."));
- if (fileName[0] == "q") {
- return;
- }
- var data = {
- selection: {
- match_id: match_id,
- user_id: user_id,
- selection: user_selection_image,
- },
- };
-
- console.log(data);
- var xhr = new XMLHttpRequest();
- xhr.open("POST", "/selection", true);
- xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
- var csrf_token = document.getElementsByName("csrf-token")[0].content;
- xhr.setRequestHeader("X-CSRF-Token", csrf_token);
- xhr.onreadystatechange = function () {
- if (xhr.readyState === XMLHttpRequest.DONE) {
- if (xhr.status === 200 || xhr.status === 201) {
- var response = JSON.parse(xhr.responseText);
- console.log(response.data);
- } else if (xhr.status === 422) {
- console.error("Error:", xhr.responseText);
- } else {
- console.error("Error:", xhr.responseText);
- }
- }
- };
- if (data != undefined && data != "string") {
- xhr.send(JSON.stringify(data));
- }
-}
-
-export { try_post };
diff --git a/app/jobs/match_broadcast_job.rb b/app/jobs/match_broadcast_job.rb
deleted file mode 100644
index 1a6d2ed..0000000
--- a/app/jobs/match_broadcast_job.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# frozen_string_literal: true
-
-# Match_Broadcast_Job
-class MatchBroadcastJob < ApplicationJob
- queue_as :default
-
- def perform(match_id, try_num, tries)
- 5.downto(0) do |seconds|
- ActionCable.server.broadcast("timer_channel_#{match_id}", { seconds:, try_num:, tries: })
- sleep 1
- end
- end
-
- after_perform do |job|
- try_num = job.arguments[1] - 1
- match = Match.find(job.arguments[0])
- sleep 3
- match.handle_missing_selections(try_num)
- UpdateWinner.new(match, try_num).update_winner
- user1_id, user2_id = match.users.ids
- selection1, selection2 = match.selections.by_try_num(try_num).group_by(&:user_id).values_at(user1_id, user2_id)
- selection1 = selection1.first
- selection2 = selection2.first
- status = selection1.status || selection2.status || 'Draw'
- broadcast(match.id, user1_id, user2_id, status, [selection1, selection2])
- sleep(0.5)
- user1_winning_selections, user2_winning_selections = match.selections.winner.group_by(&:user_id).values_at(
- user1_id, user2_id
- )
- score1 = score(user1_winning_selections)
- score2 = score(user2_winning_selections)
- try_num += 1
- monitor_tries(match, try_num, score1, score2, [selection1, selection2])
- end
-
- def score(selections)
- if selections.nil?
- 0
- else
- selections.size
- end
- end
-
- def monitor_tries(match, try_num, score1, score2, selections)
- user1_id, user2_id = match.users.ids
- if try_num < 3 || try_num == 4
- match.delayed_job(Time.zone.now + 5.seconds, try_num + 1)
- elsif score1 == score2
- handle_equal_scores(match, try_num, selections)
- elsif [3, 5].include?(try_num)
- winner_id = score1 > score2 ? user1_id : user2_id
- match.update(match_winner_id: winner_id)
- broadcast(match.id, user1_id, user2_id, "#{match.winner.name} won", selections)
- sleep 2
- generate_matches(match, selections)
- end
- end
-
- def handle_equal_scores(match, try_num, selections)
- user1_id, user2_id = match.users.ids
- if try_num == 3
- add_tries_and_broadcast(match, try_num, selections)
- elsif try_num == 5
- broadcast(match.id, user1_id, user2_id, 'Random Picking', selections)
- sleep 2
- match.update(match_winner_id: [user1_id, user2_id].sample)
- broadcast(match.id, user1_id, user2_id, "#{match.winner.name} won", selections)
- generate_matches(match, selections)
- end
- end
-
- def add_tries_and_broadcast(match, try_num, selections)
- user1_id, user2_id = match.users.ids
- match.update(tries: 5)
- broadcast(match.id, user1_id, user2_id, '2 tries added', selections)
- sleep 2
- MatchBroadcastJob.perform_later(match.id, try_num + 1, 5)
- end
-
- def broadcast(match_id, user1_id, user2_id, status, selections)
- ActionCable.server.broadcast("timer_channel_#{match_id}",
- { user1_id:, user2_id:, status:,
- selection1: selections.first.selection,
- selection2: selections.last.selection })
- end
-
- def generate_matches(match, selections)
- user1_id, user2_id = match.users.ids
- tournament = match.tournament
- current_round_remaining_matches = tournament.remaining_matches_by_round_size(match.round)
- done_matches_size = tournament.done_matches_size(match.round)
- matches_by_round_size = tournament.matches_by_round_size(match.round)
- if done_matches_size == 1 && matches_by_round_size == 1
- tournament.update_column(:tournament_winner_id, match.match_winner_id)
- broadcast(match.id, user1_id, user2_id, "Tournament Winner is #{tournament.winner.name}", selections)
- elsif current_round_remaining_matches.zero?
- tournament.create_matches(match)
- end
- end
-end
diff --git a/app/jobs/match_execution_job.rb b/app/jobs/match_execution_job.rb
new file mode 100644
index 0000000..c3971eb
--- /dev/null
+++ b/app/jobs/match_execution_job.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class MatchExecutionJob < ApplicationJob
+ queue_as :default
+
+ def perform(match_id, try_num, tries)
+ 5.downto(0) do |seconds|
+ SelectionCountdownBroadcaster.new(match_id, seconds, try_num, tries).call
+ sleep 1
+ end
+ end
+
+ after_perform do |job|
+ try_num = job.arguments[1]
+ match = Match.find(job.arguments[0])
+ match.handle_missing_selections(try_num)
+ SelectionUpdateWinner.new(match, try_num).call
+ selection1, selection2 = match.selections.by_try_num(try_num)
+ status = selection1.status || selection2.status || 'Draw Try'
+ SelectionResultBroadcaster.new(status, [selection1, selection2]).call
+ sleep 1
+ score1, score2 = match.scores
+ MatchProgressionHandler.new(match, try_num, score1, score2, [selection1, selection2]).call
+ end
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index d84cb6e..7c37ea6 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
- default from: 'from@example.com'
+ default from: 'muhammad.ramzan@techieminions.co'
layout 'mailer'
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index e49957a..9f34218 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -9,13 +9,13 @@ def initialize(user)
can %i[create new], [User]
if user.member?
can %i[create new index], Message
- can :register, Tournament
can %i[playmatch index show result all], Match
- can :create, Selection, match: { user_id: user.id }
+ can :create, TournamentsUser
+ can :create, Selection, match: { users: { id: user.id } }
can :authenticate_2fa, User
elsif user.admin?
can %i[create new index], Message
- can %i[new create create_matches index edit], Tournament
+ can %i[new create create_matches index update], Tournament
can %i[playmatch result index show new create all], Match
can :authenticate_2fa, User
end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/app/models/concerns/image_validatable.rb b/app/models/concerns/image_validatable.rb
new file mode 100644
index 0000000..c928d20
--- /dev/null
+++ b/app/models/concerns/image_validatable.rb
@@ -0,0 +1,31 @@
+# frozen_literal_string: true
+
+# app/models/concerns/image_validatable.rb
+module ImageValidatable
+ extend ActiveSupport::Concern
+
+ included do
+ validate on: %i[create update], unless: :skip_validation? do
+ validate :validate_image_type
+ validate :validate_image_size
+ end
+ end
+
+ private
+
+ def skip_validation?
+ !image.attached?
+ end
+
+ def validate_image_type
+ return unless image.attached? && !image.content_typecontent_type.in?(%w[image/png image/jpeg image/jpg])
+
+ errors.add(:image, 'must be a JPG, JPEG or PNG')
+ end
+
+ def validate_image_size
+ return unless image.attached? && image.byte_size > 5.megabytes
+
+ errors.add(:image, 'size should be less than 5MB')
+ end
+end
diff --git a/app/models/match.rb b/app/models/match.rb
index 5a9b01d..a9538c9 100644
--- a/app/models/match.rb
+++ b/app/models/match.rb
@@ -4,47 +4,71 @@
class Match < ApplicationRecord
paginates_per 5
belongs_to :tournament
- belongs_to :winner, class_name: 'User', foreign_key: 'match_winner_id', optional: true
+ belongs_to :winner, class_name: 'User', foreign_key: 'winner_id', optional: true
has_many :messages
has_many :users_matches
has_many :users, through: :users_matches
has_many :selections
scope :desc, -> { order(round: :desc) }
scope :by_round, ->(round) { where(round:) }
- scope :done, -> { where.not(match_winner_id: nil) }
- CHOICES = %w[rock rock rock].freeze
-
- def remaining_tries(user)
- done_tries = selections.by_user(user).size
- tries - done_tries
- end
+ scope :done, -> { where.not(winner_id: nil) }
+ scope :undone, -> { where(winner_id: nil) }
+ scope :won, ->(user_id) { where(winner_id: user_id) }
+ CHOICES = %w[rock paper scissor].freeze
+ after_create :schedule
+ accepts_nested_attributes_for :users_matches
def result_message(current_user_id)
- if match_winner_id == current_user_id
+ if winner_id == current_user_id
'You Won'
else
- "#{winner.name} won"
+ "#{winner&.name} won"
end
end
- def delayed_job(run_at = tournament.start_date + 10.seconds, try_num = 1)
- MatchBroadcastJob.delay(run_at:).perform_later(id, try_num, tries)
+ def schedule(run_at = time + 10.seconds, try_num = 1)
+ MatchExecutionJob.delay(run_at:).perform_later(id, try_num, tries)
end
- def set_random_choices(user_id, try_num)
- selections.create(user_id:, selection: CHOICES.sample, try_num:)
+ def add_random_choices(user_id)
+ selections.create(user_id:, choice: CHOICES.sample)
end
def handle_missing_selections(try_num)
players_selections = selections.by_try_num(try_num)
first_user_id, second_user_id = users.ids
- set_random_choices(first_user_id, try_num) unless players_selections.exists?(user_id: first_user_id)
+ add_random_choices(first_user_id) unless players_selections.exists?(user_id: first_user_id)
return if players_selections.exists?(user_id: second_user_id)
- set_random_choices(second_user_id, try_num)
+ add_random_choices(second_user_id)
+ end
+
+ def done?
+ winner_id.present?
+ end
+
+ def undone?
+ winner_id.nil?
+ end
+
+ def scores
+ users_score = selections.winner.group(:user_id).count
+ users.map { |user| users_score[user.id] || 0 }
+ end
+
+ def update_winner_on_scores
+ score1, score2 = scores
+ winner_id = score1 > score2 ? users.first.id : users.second.id
+ update(winner_id:)
+ end
+
+ def update_random_winner
+ update(winner_id: users.sample.id)
end
- def other_user(user1)
- users.where.not(id: user1.id).first
+ def players_data_by_current_user(current_user)
+ current_user_data = { current_user.id => current_user.name }
+ players_data = users.where.not(id: current_user.id).pluck(:id, :name).to_h
+ players_data.length == 1 ? current_user_data.merge(players_data) : players_data # current_user is player then place its data at first place and other player_data at last.
end
end
diff --git a/app/models/message.rb b/app/models/message.rb
index 9e6b9a3..d402a6b 100644
--- a/app/models/message.rb
+++ b/app/models/message.rb
@@ -7,6 +7,6 @@ class Message < ApplicationRecord
after_save :broadcast
def broadcast
- BroadcastMessage.send(message, name, created_at.strftime('%H:%M:%S'), match_id)
+ MessageBroadcaster.new(self).call
end
end
diff --git a/app/models/selection.rb b/app/models/selection.rb
index f8987a4..448e0ff 100644
--- a/app/models/selection.rb
+++ b/app/models/selection.rb
@@ -6,10 +6,11 @@ class Selection < ApplicationRecord
scope :winner, -> { where(winner: true) }
scope :by_user, ->(user_id) { where(user_id:) }
scope :by_try_num, ->(try_num) { where(try_num:) }
+ after_initialize :add_try_num
+ validates :try_num, uniqueness: { scope: %i[match_id user_id] }
def add_try_num
- done_tries_size = match.selections.by_user(user_id).size
- self.try_num = match.tries - (match.tries - done_tries_size)
+ self.try_num ||= match.selections.by_user(user_id).size + 1
end
def status
diff --git a/app/models/tournament.rb b/app/models/tournament.rb
index b917c84..77d0387 100644
--- a/app/models/tournament.rb
+++ b/app/models/tournament.rb
@@ -1,30 +1,33 @@
# frozen_string_literal: true
-# Tournaments:: controller
class Tournament < ApplicationRecord
paginates_per 3
+ include ImageValidatable
has_many :tournaments_users
+ has_many :users
has_many :users, through: :tournaments_users
- belongs_to :winner, class_name: 'User', foreign_key: 'tournament_winner_id', optional: true
+ belongs_to :winner, class_name: 'User', foreign_key: 'winner_id', optional: true
has_many :matches, dependent: :destroy
has_one_attached :image
validates :name, :description, :start_date, :end_date, :registration_deadline, presence: true
validate :end_date_is_after_start_date
validate :start_date_validation
validate :deadline_before_start_date
+ scope :won, ->(user_id) { where(winner_id: user_id) }
+ scope :desc, -> { order(registration_deadline: :desc) }
def deadline_before_start_date
return if registration_deadline.nil? || start_date.nil?
- return unless registration_deadline > start_date || registration_deadline < Time.zone.now
+ return if registration_deadline < start_date && registration_deadline > Time.zone.now
- errors.add(:registration_deadline, 'Registration Deadline is after Start Date')
+ errors.add(:registration_deadline, ':Please Check Registration Deadline')
end
def end_date_is_after_start_date
return if end_date.nil? || start_date.nil?
return unless end_date < start_date
- errors.add(:end_date, 'End Date is before Start Date')
+ errors.add(:end_date, ':End Date is before Start Date')
end
def start_date_validation
@@ -34,32 +37,50 @@ def start_date_validation
errors.add(:start_date, 'Start Date is in Past')
end
- def done_matches_size(round)
- matches.by_round(round).where.not(match_winner_id: nil).size
- end
-
- def remaining_matches_by_round_size(round)
- matches.by_round(round).where(match_winner_id: nil).size
- end
-
- def matches_by_round_size(round)
- matches.by_round(round).size
- end
-
def current_round_winners(round)
- users.where(id: matches.select(:match_winner_id).by_round(round))
+ users.where(id: matches.select(:winner_id).by_round(round))
end
def current_match_time
if matches.empty?
- start_date + 30.seconds
+ start_date + 60.seconds
else
Time.zone.now + 90.seconds
end
end
- def create_matches(match)
- MatchCreator.new(self, current_round_winners(match.round),
- match.round + 1).create_match
+ def create_matches(round)
+ TournamentMatchesCreator.new(self, current_round_winners(round),
+ round + 1).call
+ end
+
+ def done_matches_count_by_round(round)
+ matches.by_round(round).done.count
+ end
+
+ def matches_count_by_round(round)
+ matches.by_round(round).count
+ end
+
+ def create_next_round_matches?
+ pending_matches_count.zero?
+ end
+
+ def pending_matches_count
+ matches.undone.count
+ end
+
+ def final_match_done?(round)
+ current_round_done_matches_count = done_matches_count_by_round(round)
+ current_round_matches_count = matches_count_by_round(round)
+ current_round_done_matches_count == 1 && current_round_matches_count == 1
+ end
+
+ def generate_matches_or_update_winner(match)
+ if final_match_done?(match.round)
+ update_column(:winner_id, match.winner_id)
+ elsif create_next_round_matches?
+ create_matches(match.round)
+ end
end
end
diff --git a/app/models/tournaments_user.rb b/app/models/tournaments_user.rb
index 2b51150..50f7918 100644
--- a/app/models/tournaments_user.rb
+++ b/app/models/tournaments_user.rb
@@ -3,6 +3,5 @@
class TournamentsUser < ApplicationRecord
belongs_to :user
belongs_to :tournament
-
validates :user_id, uniqueness: { scope: :tournament_id }
end
diff --git a/app/models/user.rb b/app/models/user.rb
index e377e80..0ae82a4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,15 +1,17 @@
# frozen_string_literal: true
class User < ApplicationRecord
+ include ImageValidatable
has_many :tournaments_users
has_many :messages
has_many :tournaments, through: :tournaments_users
has_many :users_matches
has_many :matches, through: :users_matches
- has_one_attached :avatar
- scope :members, -> { where(role: 'member') }
- # enum role: %w[member admin]
-
+ has_one_attached :image
+ enum role: %w[member admin]
+ validates :phone_number, presence: true,
+ numericality: true,
+ length: { minimum: 10, maximum: 15 }
devise :registerable, :two_factor_authenticatable,
:recoverable, :rememberable, :validatable,
:trackable, :confirmable,
@@ -35,22 +37,14 @@ def total_matches_played
end
def total_matches_won
- matches.where(match_winner_id: id).size
+ matches.won(id).count
end
def total_tournaments_played
- matches.distinct.count(:tournament_id)
+ tournaments.size
end
def total_tournaments_won
- tournaments.distinct.where(tournament_winner_id: id).size
- end
-
- def member?
- role == 'member'
- end
-
- def admin?
- role == 'admin'
+ tournaments.won(id).count
end
end
diff --git a/app/models/users_match.rb b/app/models/users_match.rb
index f5b4676..e0c636e 100644
--- a/app/models/users_match.rb
+++ b/app/models/users_match.rb
@@ -3,8 +3,4 @@
class UsersMatch < ApplicationRecord
belongs_to :user
belongs_to :match
-
- def self.opponent_user_id(match_id, current_user_id)
- UsersMatch.where(match_id:).where.not(user_id: current_user_id).pluck(:user_id).first
- end
end
diff --git a/app/services/action_cable_broadcaster.rb b/app/services/action_cable_broadcaster.rb
new file mode 100644
index 0000000..8a04d53
--- /dev/null
+++ b/app/services/action_cable_broadcaster.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# broad_cast_to_a_channel_service
+class ActionCableBroadcaster
+ def initialize(channel, data)
+ @channel = channel
+ @data = data
+ end
+
+ def call
+ ActionCable.server.broadcast(@channel, @data)
+ end
+end
diff --git a/app/services/broadcast_message.rb b/app/services/broadcast_message.rb
deleted file mode 100644
index 70964a1..0000000
--- a/app/services/broadcast_message.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# broad_cast_message_service
-class BroadcastMessage
- def self.send(message, user_name, created_at, match_id)
- data = {
- message:,
- user_name:,
- created_at:
- }
- ActionCable.server.broadcast("room_channel_#{match_id}", data)
- end
-end
diff --git a/app/services/leader_board.rb b/app/services/leader_board.rb
deleted file mode 100644
index ed5438e..0000000
--- a/app/services/leader_board.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# match_creator service
-class LeaderBoard
- def result
- data = []
- User.all.each do |user|
- tournaments_won = user.total_tournaments_won
- data << { user:, tournaments_won: }
- end
- top_players(data)
- end
-
- private
-
- def top_players(data)
- top_tournaments_winners = data.sort_by! { |record| record[:tournaments_won] }.reverse!.take(10)
- top_tournaments_winners.each do |record|
- matches_played = record[:user].total_matches_played
- tournaments_played = record[:user].total_tournaments_played
- matches_won = record[:user].total_matches_won
- record.merge!({ matches_played:, tournaments_played:, matches_won: })
- end
- top_tournaments_winners
- end
-end
diff --git a/app/services/leader_board_creator.rb b/app/services/leader_board_creator.rb
new file mode 100644
index 0000000..dfad0f9
--- /dev/null
+++ b/app/services/leader_board_creator.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# leader_board_service
+class LeaderBoardCreator
+ def self.call
+ players_ids = Tournament.having('winner_id IS NOT NULL')
+ .group(:winner_id)
+ .order(count: :desc)
+ .pluck(:winner_id)
+ .take(10)
+ User.where(id: players_ids)
+ end
+end
diff --git a/app/services/match_creator.rb b/app/services/match_creator.rb
deleted file mode 100644
index 41d4c44..0000000
--- a/app/services/match_creator.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# match_creator service
-class MatchCreator
- def initialize(tournament, registered_users, round = 0)
- @tournament = tournament
- @registered_users = registered_users.shuffle
- @round = round
- end
-
- def create_match
- data = group_by_two
- matches = data[1]
- user_matches = data[0]
- Match.transaction do
- user_matches.each(&:save!)
- end
- matches.each(&:delayed_job)
- end
-
- def group_by_two
- user_matches = []
- matches = []
- match_time = @tournament.current_match_time
- @registered_users.each_slice(2) do |user1, user2|
- match = Match.create(tournament_id: @tournament.id, match_time:, round: @round)
- matches << match
- user_matches << user_match_obj(match, user1)
- user_matches << user_match_obj(match, user2)
- end
- [user_matches, matches]
- end
-
- def user_match_obj(match, user)
- UsersMatch.new(match:, user:)
- end
-end
diff --git a/app/services/match_progression_handler.rb b/app/services/match_progression_handler.rb
new file mode 100644
index 0000000..70d105f
--- /dev/null
+++ b/app/services/match_progression_handler.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class MatchProgressionHandler
+ def initialize(match, try_num, score1, score2, selections)
+ @match = match
+ @try_num = try_num
+ @score1 = score1
+ @score2 = score2
+ @selections = selections
+ end
+
+ def call
+ case @try_num
+ when 1, 2, 4
+ schedule_next_try
+ when 3, 5
+ if @score1 == @score2
+ handle_equal_scores
+ else
+ update_winner_and_broadcast_status
+ generate_matches
+ end
+ end
+ end
+
+ private
+
+ def schedule_next_try
+ @match.schedule(Time.zone.now, @try_num + 1)
+ end
+
+ def handle_equal_scores
+ if @try_num == 3
+ handle_equal_scores_three
+ elsif @try_num == 5
+ handle_equal_scores_five
+ end
+ end
+
+ def handle_equal_scores_three
+ add_tries_and_broadcast
+ schedule_next_try
+ end
+
+ def handle_equal_scores_five
+ broadcast_selections_result('Random Picking')
+ @match.update_random_winner
+ broadcast_selections_result("Match Winner is #{@match.winner.name}")
+ generate_matches
+ end
+
+ def add_tries_and_broadcast
+ @match.update(tries: 5)
+ broadcast_selections_result('2 Tries Added')
+ end
+
+ def broadcast_selections_result(status)
+ SelectionResultBroadcaster.new(status, @selections).call
+ sleep 1
+ end
+
+ def generate_matches
+ tournament = @match.tournament
+ tournament.generate_matches_or_update_winner(@match)
+ end
+
+ def update_winner_and_broadcast_status
+ @match.update_winner_on_scores
+ broadcast_selections_result("Match Winner is #{@match.winner.name}")
+ end
+end
diff --git a/app/services/message_broadcaster.rb b/app/services/message_broadcaster.rb
new file mode 100644
index 0000000..e8ac8d0
--- /dev/null
+++ b/app/services/message_broadcaster.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# message_broadcast_service
+class MessageBroadcaster < ActionCableBroadcaster
+ def initialize(message)
+ super("room_channel_#{message.match_id}", {
+ message: message.content,
+ sender_name: message.name,
+ user_id: message.user_id,
+ created_at: message.created_at.strftime('%H:%M:%S')
+ })
+ end
+end
diff --git a/app/services/selection_countdown_broadcaster.rb b/app/services/selection_countdown_broadcaster.rb
new file mode 100644
index 0000000..73aeb06
--- /dev/null
+++ b/app/services/selection_countdown_broadcaster.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# selection_time_broadcast_service
+class SelectionCountdownBroadcaster < ActionCableBroadcaster
+ def initialize(match_id, seconds, try_num, tries)
+ super("match_channel_#{match_id}", {
+ seconds:,
+ try_num:,
+ tries:
+ })
+ end
+end
diff --git a/app/services/selection_result_broadcaster.rb b/app/services/selection_result_broadcaster.rb
new file mode 100644
index 0000000..a76d76c
--- /dev/null
+++ b/app/services/selection_result_broadcaster.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# selection_result_broadcast_service
+class SelectionResultBroadcaster < ActionCableBroadcaster
+ def initialize(status, selections)
+ @status = status
+ @match = selections.first.match
+ @user1_id, @user2_id = selections.pluck(:user_id).first(2)
+ @choice1, @choice2 = selections.pluck(:choice).first(2)
+ @data = data_hash
+ super("match_channel_#{@match.id}", @match.done? ? @data.merge!(done: true) : @data)
+ end
+
+ private
+
+ def data_hash
+ {
+ status: @status,
+ user1_id: @user1_id,
+ user2_id: @user2_id,
+ choice1: @choice1,
+ choice2: @choice2
+ }
+ end
+end
diff --git a/app/jobs/update_winner.rb b/app/services/selection_update_winner.rb
similarity index 70%
rename from app/jobs/update_winner.rb
rename to app/services/selection_update_winner.rb
index 1ce0b68..97f7346 100644
--- a/app/jobs/update_winner.rb
+++ b/app/services/selection_update_winner.rb
@@ -1,12 +1,14 @@
-class UpdateWinner
+# frozen_string_literal: true
+
+class SelectionUpdateWinner
def initialize(match, try_num)
@match = match
@try_num = try_num
end
- def update_winner
- selections = @match.selections.where(try_num: @try_num)
- choice1, choice2 = selections.pluck(:selection).first(2)
+ def call
+ selections = @match.selections.by_try_num(@try_num)
+ choice1, choice2 = selections.map(&:choice)
return if choice1 == choice2
if winning_combination?(choice1, choice2)
@@ -16,6 +18,8 @@ def update_winner
end
end
+ private
+
def winning_combination?(choice1, choice2)
combinations = [%w[rock scissor], %w[scissor paper], %w[paper rock]]
combinations.include?([choice1, choice2])
diff --git a/app/services/tournament_matches_creator.rb b/app/services/tournament_matches_creator.rb
new file mode 100644
index 0000000..3e023e5
--- /dev/null
+++ b/app/services/tournament_matches_creator.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# match_creator service
+class TournamentMatchesCreator
+ def initialize(tournament, registered_users, round = 0)
+ @tournament = tournament
+ @registered_users = registered_users.shuffle
+ @round = round
+ @length = registered_users.length
+ end
+
+ def call
+ raise StandardError, 'Players should be in range 2 to 32' if @length <= 1 || @length > 32
+
+ raise StandardError, 'Players must be multiple of 8' unless (@length & (@length - 1)).zero?
+
+ time = @tournament.current_match_time
+ @registered_users.each_slice(2) do |user1, user2|
+ Match.create!(tournament_id: @tournament.id, time:, round: @round,
+ users_matches_attributes: [{ user_id: user1.id }, { user_id: user2.id }])
+ end
+ end
+end
diff --git a/app/services/two_factor_auth.rb b/app/services/two_factor_authenticator.rb
similarity index 92%
rename from app/services/two_factor_auth.rb
rename to app/services/two_factor_authenticator.rb
index 7f0c66f..65b535c 100644
--- a/app/services/two_factor_auth.rb
+++ b/app/services/two_factor_authenticator.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
# two factor authentication
-class TwoFactorAuth
+class TwoFactorAuthenticator
def initialize(user)
@user = user
end
- def send_otp_code
+ def call
@code = User.generate_otp(@user.otp_secret)
CodeMailer.send_code(@code, @user.email).deliver_now
# message = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN']).messages.create(
diff --git a/app/services/update_winner.rb b/app/services/update_winner.rb
deleted file mode 100644
index 1ce0b68..0000000
--- a/app/services/update_winner.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-class UpdateWinner
- def initialize(match, try_num)
- @match = match
- @try_num = try_num
- end
-
- def update_winner
- selections = @match.selections.where(try_num: @try_num)
- choice1, choice2 = selections.pluck(:selection).first(2)
- return if choice1 == choice2
-
- if winning_combination?(choice1, choice2)
- selections.first.update(winner: true)
- else
- selections.last.update(winner: true)
- end
- end
-
- def winning_combination?(choice1, choice2)
- combinations = [%w[rock scissor], %w[scissor paper], %w[paper rock]]
- combinations.include?([choice1, choice2])
- end
-end
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
index c04b81d..953a9b0 100644
--- a/app/views/devise/mailer/confirmation_instructions.html.erb
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -21,7 +21,7 @@
Welcome <%= @email %>!
You can confirm your account email through the link below:
-
<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+
<%= link_to "Confirm my account", confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb
index 1e58142..b1b5e23 100644
--- a/app/views/devise/mailer/reset_password_instructions.html.erb
+++ b/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -1,5 +1,5 @@
Hello <%= @resource.email %>!
Someone has requested a link to change your password. You can do this through the link below.
-<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
+<%= link_to "Change my password", edit_password_url(@resource, reset_password_token: @token) %>
If you didn't request this, please ignore this email.
Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
index 2f51880..e36bf6c 100644
--- a/app/views/devise/mailer/unlock_instructions.html.erb
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -1,4 +1,4 @@
Hello <%= @resource.email %>!
Your account has been locked due to an excessive number of unsuccessful sign in attempts.
Click the link below to unlock your account:
-<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
+<%= link_to "Unlock my account", unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/devise/registrations/_text_field.html.erb b/app/views/devise/registrations/_text_field.html.erb
deleted file mode 100644
index d982997..0000000
--- a/app/views/devise/registrations/_text_field.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index 6f78004..e850ea2 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -1,6 +1,6 @@
-
+
Edit <%= resource_name.to_s.humanize %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
@@ -8,10 +8,14 @@
@@ -33,7 +37,7 @@
<%= f.password_field :password, autocomplete: "new-password", class: "border border-gold w-full rounded" %>
<% if @minimum_password_length %>
-
<%= @minimum_password_length %> characters minimum
+
<%= @minimum_password_length %>characters minimum
<% end %>
@@ -48,7 +52,7 @@
<%= f.submit "Update", class: "px-7 py-2 btn-brown" %>
<% end %>
- <%= link_to 'Back', request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
+ <%= link_to "Back", request.referer || root_path, class: "text-gold font-black hover:text-gold_shade2" %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
index 5d634c4..6a58ad3 100644
--- a/app/views/devise/registrations/new.html.erb
+++ b/app/views/devise/registrations/new.html.erb
@@ -24,7 +24,7 @@
<%= f.label :password_confirmation, class: "text-white" %>
<%= f.password_field :password_confirmation, class: "border border-gold w-full rounded" %>
-
- Already have an account?
-
Login
+ Already have an Account?
+ <%= link_to "Log in", new_user_session_path, class: "text-gold font-black hover:text-gold_shade2" %>
<% end %>
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
index c7e3ebd..4696aa5 100644
--- a/app/views/devise/sessions/new.html.erb
+++ b/app/views/devise/sessions/new.html.erb
@@ -1,23 +1,25 @@
-
-
Sign in
+
+
Log in
<%= render "devise/shared/error_messages", resource: resource %>
<%= form_for(resource, as: resource_name, url:session_path(resource_name)) do |f| %>
<%= f.label :email, class: "text-white" %> <%= f.email_field :email,
- autofocus: true, autocomplete: "email", class: "border border-gold w-full rounded pt-3 mb-3" %>
+ autofocus: true, autocomplete: "email", class: "border border-gold w-full rounded pt-3 mb-3" %>
<%= f.label :password, class: "text-white" %>
<%= f.password_field :password, autocomplete: "current-password", class: "border border-gold w-full rounded mb-3 py-3" %>
- <%= f.submit "Sign in", class: "actions mb-4 px-10 py-3 btn-brown" %>
+ <%= f.submit "Log in", class: "actions mb-4 px-10 py-3 btn-brown" %>
+ <% end %>
+
+ Don't have an Account?
+ <%= link_to "Sign up", new_user_registration_path, class: "text-gold font-black hover:text-gold_shade2" %>
- <% end %>
+
-
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb
index f8bc70f..9e0c1c5 100644
--- a/app/views/devise/shared/_links.html.erb
+++ b/app/views/devise/shared/_links.html.erb
@@ -1,20 +1,12 @@
-<%- if controller_name != 'sessions' %>
+<%- if controller_name != "sessions" %>
<%= link_to "Log in", new_session_path(resource_name) %>
<% end %>
-<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+<%- if devise_mapping.registerable? && controller_name != "registrations" %>
<%= link_to "Sign up", new_registration_path(resource_name) %>
<% end %>
-<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
+<%- if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %>
<% end %>
-<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+<%- if devise_mapping.confirmable? && controller_name != "confirmations" %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
<% end %>
-<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
- <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %>
-<%- if devise_mapping.omniauthable? %>
- <%- resource_class.omniauth_providers.each do |provider| %>
- <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
- <% end %>
-<% end %>
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
deleted file mode 100644
index 33abe51..0000000
--- a/app/views/devise/unlocks/new.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-Resend unlock instructions
-<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
- <%= render "devise/shared/error_messages", resource: resource %>
-
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
-
-
- <%= f.submit "Resend unlock instructions" %>
-
-<% end %>
-<%= render "devise/shared/links" %>
diff --git a/app/views/layouts/_flash.js.erb b/app/views/layouts/_flash.js.erb
new file mode 100644
index 0000000..b10c3be
--- /dev/null
+++ b/app/views/layouts/_flash.js.erb
@@ -0,0 +1,15 @@
+var noticeElement = document.getElementById('flash-response-container');
+var newContent = "<%= flash[:notice] || flash[:error] %>";
+<% if flash[:error].present? %>
+ noticeElement.classList.add('bg-red-600');
+<% end %>
+if (noticeElement.innerHTML.trim() !== '') {
+ newContent = "
" + newContent;
+}
+noticeElement.insertAdjacentHTML('beforeend',newContent);
+noticeElement.style.display = 'block';
+
+setTimeout(function() {
+ noticeElement.style.display = 'none';
+ noticeElement.innerHTML = '';
+}, 2000);
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 1e403f3..2c6529f 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -5,9 +5,9 @@
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
- <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_pack_tag "application", "data-turbolinks-track": "reload" %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" %>