diff --git a/app/assets/stylesheets/dashboard.css b/app/assets/stylesheets/dashboard.css index a44d88c7..d49c4d89 100644 --- a/app/assets/stylesheets/dashboard.css +++ b/app/assets/stylesheets/dashboard.css @@ -5,7 +5,7 @@ padding: 20px; background-color: #e0e0e0; /* Light gray background */ border-radius: 10px; - margin-bottom: 20px; + margin: 20px 50px; } /* Each Stat Card */ diff --git a/app/assets/stylesheets/games.css b/app/assets/stylesheets/games.css index 307da0f3..6fd00bb8 100644 --- a/app/assets/stylesheets/games.css +++ b/app/assets/stylesheets/games.css @@ -14,6 +14,8 @@ .dropdown-content { display: none; position: absolute; + right: 0; + margin-right: 10px; background-color: #f9f9f9; border: 2px solid black; min-width: 160px; @@ -37,6 +39,35 @@ display: block; } + +.dropdown-content-account { + display: none; + position: absolute; + right: 0; + margin-right: 170px; + background-color: #f9f9f9; + border: 2px solid black; + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +.dropdown-content-account a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown-content-account a:hover { + background-color: #f1f1f1; +} + +.dropdown:hover .dropdown-content-account { + display: block; +} + .dropdown:hover .dropbtn { background-color: white; color: black; @@ -45,7 +76,7 @@ .game-list { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 20px; width: 90%; @@ -53,7 +84,10 @@ } .game-item { - padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 50px; border: 1px solid #000; text-align: center; border-radius: 20px; @@ -61,8 +95,14 @@ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); } +.game-image { + width: auto; + height: 100px; + margin-bottom: 10px; +} + .game-item:hover { - transform: scale(1.10); + transform: scale(1.04); box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.2); border: 40px; } \ No newline at end of file diff --git a/app/assets/stylesheets/spellingbee.css b/app/assets/stylesheets/spellingbee.css index 6fcbc392..d3382dae 100644 --- a/app/assets/stylesheets/spellingbee.css +++ b/app/assets/stylesheets/spellingbee.css @@ -125,7 +125,7 @@ .live-demo { border: 2px solid black; border-radius: 5 px; - padding: 60px; + padding: 5px; } .sb-table { @@ -151,3 +151,62 @@ td { border: 1px solid black; vertical-align: middle; } + +.hex-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; +} + +.hex-row { + display: flex; + justify-content: center; + gap: 0; +} + +.hex { + width: 100px; + height: 100px; + clip-path: polygon(50% 0%, 93.3% 25%, 93.3% 75%, 50% 100%, 6.7% 75%, 6.7% 25%); + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5rem; + color: var(--font-clr); + position: relative; + /* background-color: var(--primary-clr); */ + z-index: 1; +} + +/* .hex::after { + content: ""; + position: absolute; + top: -2px; + left: -2px; + width: calc(100% + 4px); + height: calc(100% + 4px); + clip-path: polygon(50% 0%, 93.3% 25%, 93.3% 75%, 50% 100%, 6.7% 75%, 6.7% 25%); + background-color: black; + z-index: 0; +} */ + +.hex-content { + position: relative; + z-index: 2; /* Ensure text is visible */ + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5rem; + color: black; +} + + +.hex-center { + background-color: var(--primary-clr); +} + +.hex:hover { + cursor: pointer; + background-color: var(--primary-clr); +} diff --git a/app/assets/stylesheets/users.css b/app/assets/stylesheets/users.css index 49de5448..f19ecbd9 100644 --- a/app/assets/stylesheets/users.css +++ b/app/assets/stylesheets/users.css @@ -29,78 +29,166 @@ caption { padding: 10px; } -.user-options { +/* Profile Container Styling */ +.profile-container { + display: grid; + grid-template-columns: 1fr 1fr; /* Two columns layout */ + gap: 20px; + width: 90%; + max-width: 1200px; + margin: 50px auto; + padding: 20px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + border-radius: 10px; + background-color: #ffffff; +} + +/* Profile Section - Information Cards */ +.profile-section, .sso-account, .role-info, .user-options-container { + background-color: #f9f9f9; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 15px; /* Reduce padding to make cards more compact */ + margin: 10px 0; +} + +/* Section Headers */ +.profile-container h3 { + font-size: 1.2rem; + color: #333; + font-family: Verdana, Geneva, Tahoma, sans-serif; + border-bottom: 2px solid #ddd; + padding-bottom: 8px; + margin-bottom: 10px; +} + +/* SSO Account Section */ +.sso-account { display: flex; - flex-direction: row; + flex-direction: column; + gap: 10px; } -.modal { -display: none; -position: fixed; -z-index: 1; -padding-top: 100px; -left: 0; -top: 0; -width: 100%; -height: 100%; -background-color: rgb(0,0,0); /* Fallback color */ -background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +.sso-btns { + display: flex; + align-items: center; + gap: 10px; /* Space between icon and text */ + padding: 8px 0; +} + +.sso-btns .btn { + background-color: #e0e0e0; + color: #333; + border-radius: 50%; + width: 35px; /* Reduce icon size */ + height: 35px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + transition: background-color 0.3s; } -.modal-content { -background-color: #fefefe; -margin: auto; -padding: 20px; -border: 1px solid #888; -width: 80%; +.sso-btns .btn:hover { + background-color: #007bff; + color: white; } -/* The Close Button */ -.close { -color: #000000; -float: right; -font-size: 28px; -font-weight: bold; +/* Account Info Text */ +.sso-info { + font-size: 0.95rem; + color: #555; } -.close:hover, -.close:focus { -color: #ee0b0b; -text-decoration: none; -cursor: pointer; +/* Role Information Section */ +.role-info ul { + list-style: none; + padding: 0; } -.user-page { +.role-info li { display: flex; - flex-direction: row; justify-content: space-between; + padding: 8px; + border-bottom: 1px solid #ddd; + font-size: 1rem; + color: #444; } -.user-options { - display: flex; - flex-direction: row; +.role-info li:last-child { + border-bottom: none; } -.sso-account { +.user-options-container { display: flex; flex-direction: column; + /* align-items: center; */ + /* padding-top: 10px; */ } -.sso-btns { +.user-options-container h3 { + margin-bottom: 10px; +} + +.user-options { display: flex; - flex-direction: row; - align-items: center; + gap: 10px; + justify-content: center; } -.sso-info { +.user-options .btn-login { + background-color: #007bff; + color: white; + border-radius: 5px; + padding: 8px 12px; /* Smaller button padding */ font-size: 1rem; - font-family: Verdana, Geneva, Tahoma, sans-serif; + transition: background-color 0.3s, transform 0.2s; } -h3 { - font-family: Verdana, Geneva, Tahoma, sans-serif; +.user-options .btn-login:hover { + background-color: #0056b3; + transform: scale(1.05); } -.role-info { - font-family: Verdana, Geneva, Tahoma, sans-serif; -} \ No newline at end of file +.user-options .dis-btn { + background-color: #ff4c4c; +} + +.user-options .dis-btn:hover { + background-color: #c0392b; +} + +.user-options-separator { + width: 90%; + height: 1px; + background-color: #ddd; + margin: 10px 0; +} + + +/* Responsive Design Adjustments */ +@media (max-width: 1024px) { + .profile-container { + grid-template-columns: 1fr; /* Stack in a single column on medium screens */ + padding: 15px; + display: flex; + flex-direction: column; + align-items: center; + } + + .profile-section, .sso-account, .role-info, .user-options-container { + width: 90%; + padding: 15px; + } +} + +@media (max-width: 768px) { + .profile-container { + grid-template-columns: 1fr; /* Single column on smaller screens */ + } + + .user-options { + flex-direction: row; /* Stack buttons horizontally */ + justify-content: center; + } +} diff --git a/app/assets/stylesheets/welcome.css b/app/assets/stylesheets/welcome.css index e22b18d8..a3a691f8 100644 --- a/app/assets/stylesheets/welcome.css +++ b/app/assets/stylesheets/welcome.css @@ -3,8 +3,25 @@ flex-direction: column; align-items: center; justify-content: center; - height: 90vh; + height: 95vh; text-align: center; + font-family: Arial, sans-serif; + animation: fadeIn 1s ease-in-out; + position: relative; +} + +.welcome-container h1 { + font-size: 2.5rem; + color: #333; + margin-bottom: 10px; + animation: slideDown 0.8s ease-out; +} + +.welcome-container p { + font-size: 1.2rem; + color: #666; + margin-bottom: 30px; + animation: slideDown 1s ease-out; } #flash { @@ -59,3 +76,44 @@ .sso-container:hover .btn-login { display: none; } + +.signature { + position: absolute; + bottom: 20px; + left: 20px; + font-size: 0.9rem; + color: #888; + font-style: italic; + animation: fadeIn 2s ease-in-out; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + transform: scale(0.95); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes slideDown { + 0% { + opacity: 0; + transform: translateY(-20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInOptions { + from { + opacity: 0; + } + to { + opacity: 1; + } +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index de7a5a6c..6c4b2082 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,9 +24,9 @@ def require_login end end - # def handle_standard_error(exception) - # logger.error(exception.message) - # reset_session - # redirect_to welcome_path, alert: "A fatal error occured. Please call support immediately" - # end + def handle_standard_error(exception) + logger.error(exception.message) + reset_session + redirect_to welcome_path, alert: "#{exception.message}" + end end diff --git a/app/controllers/boxes_controller.rb b/app/controllers/boxes_controller.rb index e5089dd9..a2e1addb 100644 --- a/app/controllers/boxes_controller.rb +++ b/app/controllers/boxes_controller.rb @@ -1,11 +1,37 @@ class BoxesController < ApplicationController + def index + BoxesService.set_week_boxes() + @boxes = LetterBox.where(play_date: Date.tomorrow..Date.tomorrow + 6).order(:play_date) + end + + def edit + @box = LetterBox.find(params[:id]) + end + + def paths + paths = BoxesService.iterative_path_search(params[:letters].chars) + respond_to do |format| + format.json { render json: { 'paths': paths } } + end + end + + def update + @box = LetterBox.find(params[:id]) + + if @box.update(params.require(:letter_box).permit(:letters)) + redirect_to edit_box_path(@box), notice: "Letter Boxed for #{@box.play_date.strftime("%B %d")} updated successfully!" + else + redirect_to edit_box_path(@box), alert: "Invalid update" + end + end + def play @aesthetic = Aesthetic.find_by(game_id: Game.find_by(name: "Letter Boxed").id) @letter_box = LetterBox.find_by(play_date: Date.today) while @letter_box.nil? @letter_box = LetterBox.create( - letters: "clu-esi-toj-xnk", + letters: "clueiostjxnk", play_date: Date.today ) end diff --git a/app/models/letter_box.rb b/app/models/letter_box.rb index b83abb1a..7a266dd6 100644 --- a/app/models/letter_box.rb +++ b/app/models/letter_box.rb @@ -1,12 +1,16 @@ class LetterBox < ApplicationRecord - validates :letters, presence: true + validates :letters, presence: true, length: { is: 12 } validates :play_date, presence: true, uniqueness: true + validate :lb_letters + private - def self.today - find_by(play_date: Date.today) + def lb_letters + unless letters =~ /\A[a-zA-Z]+\z/ + errors.add(:letters, "#{letters} must contain only letters") end - def letters_by_side - letters.split("-") + unless letters.chars.uniq.length == letters.length + errors.add(:letters, "#{letters} must contain unique characters") end + end end diff --git a/app/services/boxes_service.rb b/app/services/boxes_service.rb new file mode 100644 index 00000000..1b905e0b --- /dev/null +++ b/app/services/boxes_service.rb @@ -0,0 +1,75 @@ +class BoxesService + def self.set_week_boxes + tomorrow = Date.tomorrow + boxes = LetterBox.where(play_date: tomorrow...tomorrow + 7).order(:play_date) + play_date = boxes.any? ? boxes.maximum(:play_date) : tomorrow + (play_date..tomorrow + 6).each do |date| + self.set_day_box(date) + end + end + + def self.set_day_box(date = Date.today) + while LetterBox.find_by(play_date: date).nil? + alphabet = ("a".."z").to_a + letters = alphabet.shuffle[0, 12] + + while ([ "a", "e", "i", "o", "u" ]-letters).length > 2 + letters = alphabet.shuffle[0, 12] + end + + if iterative_path_search(letters).length >= 3 + LetterBox.create(letters: letters.join, play_date: date) + end + end + end + + def self.iterative_path_search(letters, num_of_paths = 3) + paths = [] + max_depth = 2 + possible_words = {} + patterns = [ letters[0..2], letters[3..5], letters[6..8], letters[9..11] ] + letters.each_with_index do |l, i| + possible_letters = letters.map(&:clone) + possible_letters.slice!(3*(i/3), 3) + possible_letters.insert(0, l) + words = WordsService.words_by_first_letter(possible_letters.join) + patterns.each do |p| + words = words.select { |w| not w.match(/[#{p}][#{p}]+/) } + end + + return paths if words.empty? + + possible_words[l] = words + end + while max_depth < 6 and paths.length < num_of_paths + find_paths(max_depth, num_of_paths, paths, possible_words, letters, letters) + max_depth += 1 + end + + paths + end + + def self.find_paths(max_depth, num_of_paths, valid_paths, possible_words, remaining_letters, last_word = nil, curr_path = [], depth = 0) + if remaining_letters.empty? and not valid_paths.include? curr_path + valid_paths << curr_path + end + + if valid_paths.length >= num_of_paths + false + else + if depth < max_depth + if last_word.nil? + words = possible_words.values.flatten.select { |w| not curr_path.include? w } + else + words = possible_words[last_word.last].select { |w| not curr_path.include? w } + end + words.sort_by { |w| (remaining_letters-w.chars.uniq).length } + .each do |word| + still_searching = find_paths(max_depth, num_of_paths, valid_paths, possible_words, remaining_letters - word.chars.uniq, word, curr_path+[ word ], depth + 1) + return if not still_searching + end + end + true + end + end +end diff --git a/app/services/oauth_service.rb b/app/services/oauth_service.rb index ac6051d5..ce27110e 100644 --- a/app/services/oauth_service.rb +++ b/app/services/oauth_service.rb @@ -38,10 +38,9 @@ def existing_user def new_user user = find_or_create_user - + if user.valid? - Role.create!(user_id: user.id, role: "Member") - Settings.create!(user_id: user.id, active_roles: "Member") + init_new_user(user.id) { success: true, user: user } else { success: false, alert: "Login failed." } @@ -76,7 +75,7 @@ def google_user first_name = names[0].presence || "User" last_name = names[1..].join(" ").presence || "" - User.find_or_create_by(email: email) do |user| + user = User.find_or_create_by(email: email) do |user| user.first_name = first_name user.last_name = last_name end @@ -105,4 +104,13 @@ def spotify_user user.last_name = last_name end end + + def init_new_user(user_id) + user = User.find(user_id) + if user.created_at == user.updated_at + user.touch + Role.create!(user_id: user.id, role: "Member") + Settings.create!(user_id: user.id, active_roles: "Member") + end + end end diff --git a/app/services/words_service.rb b/app/services/words_service.rb index c9ebe950..a4936929 100644 --- a/app/services/words_service.rb +++ b/app/services/words_service.rb @@ -37,7 +37,22 @@ def self.words(letters) end.map { |word_data| word_data["word"] } usable_words - end + end + + # This method returns the list of valid words that can be formed by a given sequence of letters and START with the first letter + + def self.words_by_first_letter(letters) + uri = URI("https://api.datamuse.com/words?sp=#{URI.encode_www_form_component("#{letters[0]}*+#{letters}")}&md=f") + response = Net::HTTP.get(uri) + words = JSON.parse(response) + + usable_words = words.select do |word_data| + frequency = word_data["tags"][0][/\d+\.\d+/].to_f + word_data["word"].length > 3 && frequency > 0.5 && word_data["word"] =~ /^[a-zA-Z]+$/ + end.map { |word_data| word_data["word"] } + + usable_words + end # This method checks if a given word is valid # diff --git a/app/views/bees/spellingbee.html.erb b/app/views/bees/spellingbee.html.erb index bf3da493..9dd0b79b 100644 --- a/app/views/bees/spellingbee.html.erb +++ b/app/views/bees/spellingbee.html.erb @@ -39,17 +39,36 @@
Date | +Letters | +Edit | +|||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
<%= box.play_date.strftime("%B %d") %> | +<%= box.letters[0] %> | +<%= box.letters[1] %> | +<%= box.letters[2] %> | +<%= box.letters[3] %> | +<%= box.letters[4] %> | +<%= box.letters[5] %> | +<%= box.letters[6] %> | +<%= box.letters[7] %> | +<%= box.letters[8] %> | +<%= box.letters[9] %> | +<%= box.letters[10] %> | +<%= box.letters[11] %> | ++ <%= link_to "".html_safe, edit_box_path(box), title: "Edit Letters", class: "btn btn-login btn-edit" %> + | +
First Name: <%= @current_user.first_name %>
Last Name: <%= @current_user.last_name %>
Please select one of the options to continue:
- -