Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
da6c0d9
delete link_tree in manifest.js
PavelPo99 Apr 2, 2025
39e2b87
preparing for deploy
PavelPo99 Apr 14, 2025
eed8055
fixed file puma
PavelPo99 Apr 14, 2025
26184af
update port
PavelPo99 Apr 14, 2025
e9ed0cd
add file render-build
PavelPo99 Apr 14, 2025
0132b57
change port
PavelPo99 Apr 14, 2025
6907934
fixed mistake render-build
PavelPo99 Apr 14, 2025
8ed86d7
fixed mistake render-build
PavelPo99 Apr 14, 2025
43a44b7
fixed db
PavelPo99 Apr 15, 2025
ff0462f
fixed assets
PavelPo99 Apr 15, 2025
912bd07
add mailer
PavelPo99 Apr 15, 2025
337efce
fixed mailer
PavelPo99 Apr 15, 2025
6880908
fixed mailer
PavelPo99 Apr 16, 2025
44e9123
setting up mail
PavelPo99 Apr 16, 2025
a9f7075
setting up mail #2
PavelPo99 Apr 16, 2025
aae9fe6
setting up mail #3
PavelPo99 Apr 16, 2025
7e8e878
setting up mail #4
PavelPo99 Apr 16, 2025
7535562
add tests
PavelPo99 Apr 16, 2025
cae9cdb
upgarde navbar
PavelPo99 Apr 17, 2025
b0d183a
fixed bug navbar, add feedback
PavelPo99 Apr 20, 2025
bf8c34b
dug branch
PavelPo99 Apr 20, 2025
0e54f15
create feedback form
PavelPo99 Apr 28, 2025
4c2ddf4
add new navbar
PavelPo99 Apr 28, 2025
cc0e438
add db feedback
PavelPo99 Apr 28, 2025
3533dd0
final
PavelPo99 May 5, 2025
6e955cc
fixed migration
PavelPo99 May 5, 2025
ebdf116
fixed mailer
PavelPo99 May 5, 2025
768fb32
fixed delete test, send email
PavelPo99 May 6, 2025
0744879
task badge done
PavelPo99 Jun 19, 2025
16956c5
fixed import scss
PavelPo99 Jun 19, 2025
81ce2b6
delete console in web
PavelPo99 Jun 19, 2025
9cdafa6
corrected the comments
PavelPo99 Jul 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ group :test do
gem "capybara"
gem "selenium-webdriver"
end

gem "faraday-retry", "~> 2.3"
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ GEM
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
faraday-retry (2.3.1)
faraday (~> 2.0)
ffi (1.17.1-x86_64-linux-gnu)
globalid (1.2.1)
activesupport (>= 6.1)
Expand Down Expand Up @@ -369,6 +371,7 @@ DEPENDENCIES
devise (~> 4.9)
dotenv-rails
faker (~> 3.5, >= 3.5.1)
faraday-retry (~> 2.3)
importmap-rails
jbuilder
jquery-rails
Expand Down
7 changes: 4 additions & 3 deletions app/assets/config/manifest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//= link_tree ../images
//= link_tree ../builds
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
//= link_directory ../stylesheets .scss
//= link application.css
// = link jquery.min.js
//= link jquery.min.js
//= link application.scss
//= link badge/style.scss
//= link nav.scss
4 changes: 2 additions & 2 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import "bootstrap";

@import "../node_modules/bootstrap/scss/bootstrap";
@import "typography/icons";
@import "global";
@import "badge/style";
44 changes: 44 additions & 0 deletions app/assets/stylesheets/badge/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.earned-badge {
position: relative;
border: 2px solid #FFD700;
transition: all 0.3s ease;
}

.earned-badge:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(255, 215, 0, 0.2);
}

.earned-ribbon {
position: absolute;
top: 10px;
right: -10px;
background: #FFD700;
color: #000;
padding: 3px 15px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 1;
}

.earned-ribbon:before {
content: '';
position: absolute;
bottom: -10px;
right: 0;
border-left: 10px solid transparent;
border-right: 10px solid #c90;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
}

.badge-icon {
max-height: 100px;
filter: drop-shadow(0 2px 5px rgba(0,0,0,0.2));
}

.grayscale {
filter: grayscale(100%) brightness(0.7);
}
30 changes: 30 additions & 0 deletions app/assets/stylesheets/nav.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.navbar {
padding: 0.75rem 1rem;
transition: all 0.3s ease;
}

.navbar-brand {
font-size: 1.25rem;
transition: all 0.3s ease;
}

.nav-link {
position: relative;
padding: 0.5rem 1rem;
}

.nav-link:after {
content: '';
position: absolute;
bottom: 0;
left: 1rem;
right: 1rem;
height: 2px;
background: rgba(255, 255, 255, 0.5);
transform: scaleX(0);
transition: transform 0.3s ease;
}

.nav-link:hover:after {
transform: scaleX(1);
}
49 changes: 49 additions & 0 deletions app/controllers/admin/badges_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class Admin::BadgesController < ApplicationController
include BadgesHelper
before_action :set_badge, only: [ :show, :edit, :update, :destroy ]

def index
@badges = Badge.all
end

def new
@badge = Badge.new
end

def create
@badge = Badge.new(badge_params)
if @badge.save
redirect_to admin_badges_path, notice: t("admin.badge.create.success")
else
render :new
end
end

def update
if @badge.update(badge_params)
redirect_to admin_badges_path, notice: t("admin.badge.update.success")
else
render :edit
end
end

def edit
@rules = Badge.available_badge_rules
end

def destroy
@badge.destroy
redirect_to admin_badges_path, notice: t("admin.badge.delete.success")
end


private

def set_badge
@badge = Badge.find(params[:id])
end

def badge_params
params.require(:badge).permit(:title, :text, :rule, :image_url)
end
end
11 changes: 11 additions & 0 deletions app/controllers/badges_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class BadgesController < ApplicationController
include BadgesHelper

before_action :authenticate_user!

def index
@all_badges = Badge.all
@my_badge_ids = current_user.badges.pluck(:id)
@badge_counts = current_user.badge_users.group(:badge_id).count
end
end
25 changes: 25 additions & 0 deletions app/controllers/feedbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class FeedbacksController < ApplicationController
before_action :authenticate_user!
def new
@feedback = Feedback.new
end

def create
@feedback = Feedback.new(feedback_params)
@feedback.email = current_user.email
@feedback.author = current_user

if @feedback.valid?
FeedbackMailer.feedback_email(@feedback).deliver_later
redirect_to root_path, notice: t(".success")
else
render :new
end
end

private

def feedback_params
params.require(:feedback).permit(:name, :message)
end
end
15 changes: 11 additions & 4 deletions app/controllers/test_passages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
class TestPassagesController < ApplicationController

before_action :authenticate_user!
before_action :set_test_passage, only: %i[ show result update ]

Expand All @@ -8,7 +7,7 @@ def show; end
def result; end

def update
if @test_passage.question_any?(params)
if @test_passage.question_any?(params)
@test_passage.accept!(params[:answer_ids])

completed_test
Expand All @@ -25,8 +24,16 @@ def set_test_passage

def completed_test
if @test_passage.completed?

TestMailer.completed_test(@test_passage).deliver_now

TestMailer.completed_test(@test_passage).deliver_later

if @test_passage.test_successful?
new_badges = BadgeAwardService.new(@test_passage).call

if new_badges.any?
flash[:notice] = t("test_passages.badge", name_badge: new_badges.map(&:title).join(", "))
end
end

redirect_to result_test_passage_path(@test_passage)
else
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/admin/badges_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Admin::BadgesHelper
end
40 changes: 40 additions & 0 deletions app/helpers/badges_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module BadgesHelper
def available_badge_rules
{
first_attempt: {
description: I18n.t("activerecord.badge.first_attempt")
},
all_tests_in_category: {
description: I18n.t("activerecord.badge.all_tests_in_category"),
options: Category.pluck(:title, :id)
},
all_tests_of_level: {
description: I18n.t("activerecord.badge.all_tests_of_level"),
options: (1..5).to_a
}
}.with_indifferent_access
end

def badge_rule_description(badge)
return badge.rule unless badge.rule.present?

rule_parts = badge.rule.split(":")
rule_name = rule_parts[0]
param = rule_parts[1]

rule_data = available_badge_rules[rule_name]
return badge.rule unless rule_data

base_description = rule_data[:description]

case rule_name
when "all_tests_in_category"
category = Category.find_by(id: param)
category ? "#{base_description} '#{category.title}'" : base_description
when "all_tests_of_level"
"#{base_description} #{param}"
else
base_description
end
end
end
2 changes: 2 additions & 0 deletions app/helpers/feedbacks_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module FeedbacksHelper
end
6 changes: 4 additions & 2 deletions app/javascript/utilities/progress_bar.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
document.addEventListener('turbo:load', function() {
if (document.URL.includes('/test_passages/')) {
const testPassageUrlPattern = /\/test_passages\/\d+$/;

if (testPassageUrlPattern.test(document.URL)) {
const progress_bars = document.querySelector('.progress-bar');

const totalQuestion = progress_bars.dataset.totalQuestion
let currentQuestion = progress_bars.dataset.currentQuestion

let progress_test = (100 / totalQuestion) * (currentQuestion - 1)
progress_bars.style = "width: " + progress_test + "%"
progress_bars.ariaValuenow = progress_test
progress_bars.setAttribute('aria-valuenow', progress_test)
}
})
70 changes: 70 additions & 0 deletions app/javascript/utilities/rules_badge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function initBadgeRules() {
const ruleTypeSelect = document.getElementById('rule_type_select');
if (!ruleTypeSelect) return;

const paramContainer = document.getElementById('rule_parameter_container');
const paramSelect = document.getElementById('rule_parameter_select');
const ruleField = document.getElementById('rule_field');

const badgeRulesData = document.getElementById('badge-rules-data');
const rules = JSON.parse(badgeRulesData.dataset.rules);
const currentRule = badgeRulesData.dataset.currentRule || '';

updateRuleField();

ruleTypeSelect.addEventListener('change', function() {
const ruleType = this.value;
const ruleData = rules[ruleType];

paramContainer.classList.add('d-none');
paramSelect.innerHTML = '';

if (ruleType && ruleData.options) {
ruleData.options.forEach(option => {
const [text, value] = Array.isArray(option) ? option : [option, option];
const opt = document.createElement('option');
opt.value = value;
opt.textContent = text;
paramSelect.appendChild(opt);
});

paramContainer.classList.remove('d-none');
}

updateRuleField();
});

paramSelect.addEventListener('change', updateRuleField);

function updateRuleField() {
const ruleType = ruleTypeSelect.value;
const param = paramSelect.value;

if (ruleType) {
ruleField.value = param ? `${ruleType}:${param}` : ruleType;
} else {
ruleField.value = '';
}
}

if (currentRule) {
const [ruleType, param] = currentRule.split(':');

if (ruleType) {
ruleTypeSelect.value = ruleType;

const event = new Event('change');
ruleTypeSelect.dispatchEvent(event);

setTimeout(() => {
if (param && paramSelect) {
paramSelect.value = param;
updateRuleField();
}
}, 100);
}
}
}

document.addEventListener('DOMContentLoaded', initBadgeRules);
document.addEventListener('turbo:load', initBadgeRules);
4 changes: 2 additions & 2 deletions app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: %{"TestGuru" <mail@testguru.com>}
layout 'mailer'
default from: "TestGuru <#{ENV.fetch('SMTP_USERNAME', nil)}>"
layout "mailer"
end
11 changes: 11 additions & 0 deletions app/mailers/feedback_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class FeedbackMailer < ApplicationMailer
default from: -> { ENV.fetch("SMTP_USERNAME") || "feedback@testguru.com" }

def feedback_email(feedback)
@feedback = feedback
mail(
to: ENV.fetch("EMAIL_TO_ADMIN", "admin@testguru.com"),
subject: "New feedback from #{feedback.name}"
)
end
end
Loading
Loading