diff --git a/app/authorizers/content_authorizer.rb b/app/authorizers/content_authorizer.rb
index eb714272..e3a074be 100644
--- a/app/authorizers/content_authorizer.rb
+++ b/app/authorizers/content_authorizer.rb
@@ -1,10 +1,16 @@
class ContentAuthorizer < ApplicationAuthorizer
def readable_by? user
- return true if user && user.site_administrator?
- return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
- return true if ::PermissionService.user_owns_content?(user: user, content: resource)
+ # Check public content first - these should be accessible to anyone even without a user account
return true if ::PermissionService.content_is_public?(content: resource)
return true if ::PermissionService.content_is_in_a_public_universe?(content: resource)
+
+ # If no user is provided (logged out), they can only access public content
+ return false if user.nil?
+
+ # Otherwise check user-specific permissions
+ return true if user.site_administrator?
+ return true if ::PermissionService.user_owns_any_containing_universe?(user: user, content: resource)
+ return true if ::PermissionService.user_owns_content?(user: user, content: resource)
return true if ::PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource)
return false
diff --git a/app/authorizers/core_content_authorizer.rb b/app/authorizers/core_content_authorizer.rb
index c21d9582..75cbc0ec 100644
--- a/app/authorizers/core_content_authorizer.rb
+++ b/app/authorizers/core_content_authorizer.rb
@@ -1,8 +1,14 @@
class CoreContentAuthorizer < ContentAuthorizer
def self.readable_by?(user)
- return true if user && resource.user == user # PermissionService.user_owns_content?()
+ # Check public content first - these should be accessible to anyone
return true if resource.privacy == 'public'
return true if resource.try(:universe).try(:privacy) == 'public'
+
+ # For private content, require a user
+ return false if user.nil?
+
+ # Check user-specific permissions
+ return true if resource.user == user # PermissionService.user_owns_content?()
false
end
diff --git a/app/controllers/browse_controller.rb b/app/controllers/browse_controller.rb
index 99bde280..ae5bca64 100644
--- a/app/controllers/browse_controller.rb
+++ b/app/controllers/browse_controller.rb
@@ -27,8 +27,10 @@ class BrowseController < ApplicationController
# Go through each content type and find public items with this tag
Rails.application.config.content_types[:all].each do |content_type|
- # First find page IDs with this tag
- tag_page_ids = PageTag.where(page_type: content_type.name, slug: @tag_slug).pluck(:page_id)
+ # First find page IDs with this tag - case insensitive matching for slug
+ tag_page_ids = PageTag.where(page_type: content_type.name)
+ .where("LOWER(slug) = ?", @tag_slug.downcase)
+ .pluck(:page_id)
if tag_page_ids.any?
# Use database-level randomization with the daily seed for caching potential
@@ -49,7 +51,9 @@ class BrowseController < ApplicationController
end
# Add documents separately since they don't use the common content type structure
- document_tag_page_ids = PageTag.where(page_type: 'Document', slug: @tag_slug).pluck(:page_id)
+ document_tag_page_ids = PageTag.where(page_type: 'Document')
+ .where("LOWER(slug) = ?", @tag_slug.downcase)
+ .pluck(:page_id)
if document_tag_page_ids.any?
documents = Document.where(id: document_tag_page_ids)
.where(privacy: 'public')
@@ -65,7 +69,9 @@ class BrowseController < ApplicationController
end
# Add timelines separately since they don't use the common content type structure
- timeline_tag_page_ids = PageTag.where(page_type: 'Timeline', slug: @tag_slug).pluck(:page_id)
+ timeline_tag_page_ids = PageTag.where(page_type: 'Timeline')
+ .where("LOWER(slug) = ?", @tag_slug.downcase)
+ .pluck(:page_id)
if timeline_tag_page_ids.any?
timelines = Timeline.where(id: timeline_tag_page_ids)
.where(privacy: 'public')
diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb
index c048e471..a7919209 100644
--- a/app/controllers/content_controller.rb
+++ b/app/controllers/content_controller.rb
@@ -89,10 +89,10 @@ class ContentController < ApplicationController
def show
content_type = content_type_from_controller(self.class)
- return redirect_to(root_path, notice: "That page doesn't exist!") unless valid_content_types.include?(content_type.name)
+ return redirect_to(root_path, notice: "That page doesn't exist!", status: :not_found) unless valid_content_types.include?(content_type.name)
@content = content_type.find_by(id: params[:id])
- return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil?
+ return redirect_to(root_path, notice: "You don't have permission to view that content.", status: :not_found) if @content.nil?
return redirect_to(root_path) if @content.user.nil? # deleted user's content
return if ENV.key?('CONTENT_BLACKLIST') && ENV['CONTENT_BLACKLIST'].split(',').include?(@content.user.try(:email))
diff --git a/app/controllers/document_analyses_controller.rb b/app/controllers/document_analyses_controller.rb
index e36a0ccb..08b974e9 100644
--- a/app/controllers/document_analyses_controller.rb
+++ b/app/controllers/document_analyses_controller.rb
@@ -47,7 +47,7 @@ class DocumentAnalysesController < ApplicationController
def authorize_user_for_document
unless @document.present? && (current_user || User.new).can_read?(@document)
- redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
+ redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
return false
end
end
diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb
index 71423c0e..822f6970 100644
--- a/app/controllers/documents_controller.rb
+++ b/app/controllers/documents_controller.rb
@@ -68,11 +68,11 @@ class DocumentsController < ApplicationController
def show
unless @document.present? && (current_user || User.new).can_read?(@document)
- return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
+ return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
end
if @document.user.thredded_user_detail.moderation_state == "blocked"
- return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
+ return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
end
# Put the focus on the document by removing Notebook.ai actions
@@ -81,7 +81,7 @@ class DocumentsController < ApplicationController
def analysis
unless @document.present? && (current_user || User.new).can_read?(@document)
- redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
+ redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
end
@analysis = @document.document_analysis.where.not(queued_at: nil).order('updated_at DESC').first
@@ -98,7 +98,7 @@ class DocumentsController < ApplicationController
end
def queue_analysis
- return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!") unless @document.present?
+ return redirect_back(fallback_location: documents_path, notice: "That document doesn't exist!", status: :not_found) unless @document.present?
return redirect_back(fallback_location: documents_path, notice: "Document analysis is a feature for Premium users.") unless @document.user.on_premium_plan?
return redirect_back(fallback_location: documents_path, notice: "You don't have permission to do that!") unless @document.user == current_user
@@ -214,7 +214,7 @@ class DocumentsController < ApplicationController
def plaintext
unless @document.present? && (current_user || User.new).can_read?(@document)
- return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.")
+ return redirect_to(root_path, notice: "That document either doesn't exist or you don't have permission to view it.", status: :not_found)
end
render layout: 'plaintext'
@@ -335,7 +335,7 @@ class DocumentsController < ApplicationController
@document = Document.find_by(id: params[:id])
unless @document
- redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!"
+ redirect_to root_path, notice: "Either that document doesn't exist or you don't have permission to view it!", status: :not_found
return
end
end
diff --git a/app/controllers/universes_controller.rb b/app/controllers/universes_controller.rb
index ca1484fe..8eb03810 100644
--- a/app/controllers/universes_controller.rb
+++ b/app/controllers/universes_controller.rb
@@ -7,7 +7,7 @@ class UniversesController < ContentController
@content_type = content_type_name.to_s.singularize.capitalize.constantize
@universe = Universe.find_by(id: params[:id])
- return redirect_to(root_path, notice: "That universe doesn't exist!") unless @universe.present?
+ return redirect_to(root_path, notice: "That universe doesn't exist!", status: :not_found) unless @universe.present?
@content_list = @universe.send(content_type_name)
# todo just use current_user.can_view?(@universe) and/or individual filtering
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2d994640..817c28e6 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -99,7 +99,10 @@ class UsersController < ApplicationController
@tag_slug = params[:tag_slug]
@tag = PageTag.find_by(user_id: @user.id, slug: @tag_slug)
- return redirect_to(profile_by_username_path(username: @user.username), notice: 'That tag does not exist.') if @tag.nil?
+ if @tag.nil?
+ redirect_url = @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user)
+ return redirect_to(redirect_url, notice: 'That tag does not exist.', status: :not_found)
+ end
# Find all public content with this tag
@tagged_content = []
@@ -181,7 +184,7 @@ class UsersController < ApplicationController
def set_user
@user = User.find_by(user_params)
- return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil?
+ return redirect_to(root_path, notice: 'That user does not exist.', status: :not_found) if @user.nil?
return redirect_to(root_path, notice: 'That user has chosen to hide their profile.') if @user.private_profile?
return redirect_to(root_path, notice: 'That user has had their profile hidden.') if @user.thredded_user_detail.moderation_state == 'blocked'
diff --git a/app/services/permission_service.rb b/app/services/permission_service.rb
index bfc15d90..c7820b85 100644
--- a/app/services/permission_service.rb
+++ b/app/services/permission_service.rb
@@ -25,11 +25,18 @@ class PermissionService < Service
end
def self.user_can_contribute_to_containing_universe?(user:, content:)
+ # Early return if no user is provided
return false if user.nil?
+
+ # Special case for attribute-related content
return true if [AttributeCategory, AttributeField, Attribute].include?(content.class) #todo audit this
- return true if user.contributable_universe_ids.include?(content.universe_id)
- return true if user.universes.pluck(:id).include?(content.universe_id)
+ # Handle cases where content might not have a universe_id
+ return false if content.universe_id.nil?
+
+ # Check if user can contribute to this universe
+ return true if user.respond_to?(:contributable_universe_ids) && user.contributable_universe_ids.include?(content.universe_id)
+ return true if user.respond_to?(:universes) && user.universes.pluck(:id).include?(content.universe_id)
return false
end
diff --git a/app/views/browse/tag.html.erb b/app/views/browse/tag.html.erb
index 575e9857..b990012b 100644
--- a/app/views/browse/tag.html.erb
+++ b/app/views/browse/tag.html.erb
@@ -80,7 +80,7 @@
category
- <%= pluralize(content_types_count, 'category') %>
+ <%= pluralize(content_types_count, 'page type') %>
@@ -106,20 +106,20 @@
-
+
filter_list
Filter by category:
-
+
layers
All (<%= total_items %>)
<% @tagged_content.each do |content_group| %>
-
+
<%= content_group[:icon] %>
<%= content_group[:type].pluralize %> (<%= content_group[:content].count %>)
@@ -453,12 +453,23 @@
.modal-how-to-join {
border-radius: 8px;
max-width: 500px;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .modal-how-to-join {
+ background-color: #2D2D31 !important;
+ color: #ddd;
}
.modal-how-to-join h4 {
margin-top: 0;
font-size: 20px;
font-weight: 500;
+ transition: color 1s ease;
+ }
+
+ body.dark .modal-how-to-join h4 {
+ color: white;
}
.modal-how-to-join ol {
@@ -473,6 +484,17 @@
background-color: #f3e5f5;
padding: 2px 5px;
border-radius: 3px;
+ transition: background-color 1s ease, color 1s ease;
+ }
+
+ body.dark .modal-how-to-join .highlight {
+ background-color: rgba(156, 39, 176, 0.2);
+ color: white;
+ }
+
+ /* Fix modal content border */
+ body.dark .modal-content {
+ border-color: rgba(255, 255, 255, 0.1) !important;
}
:root {
@@ -545,6 +567,39 @@
}
/* Category filters styling */
+ .category-filters {
+ background-color: #fff;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .category-filters {
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+
+ .category-filter {
+ background-color: #f5f5f5;
+ color: #333;
+ transition: background-color 1s ease, color 1s ease;
+ }
+
+ body.dark .category-filter {
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ /* Ensure filter text is always readable regardless of theme */
+ .category-filter {
+ color: #333 !important;
+ }
+
+ /* Fix the filter title text in dark mode */
+ body.dark .category-filter-title span {
+ color: #ddd;
+ }
+
+ body.dark .category-filter.active {
+ color: #333;
+ }
+
@media only screen and (max-width: 600px) {
.category-filters {
overflow-x: auto;
@@ -580,10 +635,16 @@
/* Better card styling */
.hoverable.card {
- transition: box-shadow 0.25s, transform 0.25s;
+ transition: box-shadow 0.25s, transform 0.25s, background-color 1s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
border-radius: 8px;
margin-bottom: 8px;
+ background-color: #fff;
+ }
+
+ body.dark .hoverable.card {
+ background-color: rgba(255, 255, 255, 0.1);
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.hoverable.card:hover {
@@ -591,11 +652,20 @@
transform: translateY(-2px);
}
+ body.dark .hoverable.card:hover {
+ box-shadow: 0 5px 15px rgba(0,0,0,0.4);
+ }
+
/* Position relative for absolute positioning within */
.hoverable.card {
position: relative;
padding-bottom: 8px;
border-bottom: 4px solid #eeeeee;
+ transition: border-color 1s ease;
+ }
+
+ body.dark .hoverable.card {
+ border-bottom: 4px solid rgba(255, 255, 255, 0.1);
}
.js-content-card-container > a,
@@ -623,6 +693,11 @@
overflow: hidden;
position: relative;
padding-bottom: 20px !important;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .card .card-content {
+ background-color: rgba(255, 255, 255, 0.1);
}
.tags-container {
@@ -653,6 +728,11 @@
height: 15px;
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%);
pointer-events: none;
+ transition: background 1s ease;
+ }
+
+ body.dark .card .card-content:after {
+ background: linear-gradient(to bottom, rgba(40,40,45,0) 0%, rgba(40,40,45,1) 100%);
}
/* Card footer line - more visible */
@@ -666,5 +746,24 @@
background-color: #f0f0f0;
border-radius: 0 0 8px 8px;
z-index: 1;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .card::after {
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ /* Dark mode text styles */
+ body.dark .card .card-content span[style*="color: #424242"] {
+ color: #ddd !important;
+ }
+
+ body.dark .grey-text {
+ color: #aaa !important;
+ }
+
+ body.dark .tag-pill[style*="background-color: #f0f0f0"] {
+ background-color: rgba(255, 255, 255, 0.1) !important;
+ color: #aaa !important;
}
\ No newline at end of file
diff --git a/app/views/content/form/images/_edit_list.html.erb b/app/views/content/form/images/_edit_list.html.erb
index 2911cbb8..b1c4c8df 100644
--- a/app/views/content/form/images/_edit_list.html.erb
+++ b/app/views/content/form/images/_edit_list.html.erb
@@ -8,7 +8,7 @@
<% raw_model.image_uploads.each do |image| %>
- <%= link_to image.src(:original), class: 'z-depth-1 hoverable right', target: '_new' do %>
+ <%= link_to image.src(:original), class: 'z-depth-1 hoverable right card', target: '_new' do %>
<%= image_tag image.src(:small) %>
<% end %>
@@ -20,9 +20,9 @@
- <%= link_to 'Open', image.src(:original), class: 'btn white black-text', target: '_new' %>
+ <%= link_to 'Open', image.src(:original), class: 'btn black-text', target: '_new' %>
<%= link_to 'Delete', image_deletion_path(image.id),
- class: 'btn white black-text js-remove-image',
+ class: 'btn black-text js-remove-image',
method: 'delete',
remote: true,
data: { confirm: "Are you sure? This can't be undone." } %>
@@ -35,7 +35,7 @@
<% @basil_images.each do |commission| %>
- <%= link_to commission.image, class: 'z-depth-1 hoverable right', target: '_new' do %>
+ <%= link_to commission.image, class: 'z-depth-1 hoverable right card', target: '_new' do %>
<%= image_tag commission.image, style: 'max-width: 100%' %>
<% end %>
@@ -47,9 +47,9 @@
- <%= link_to 'Open', commission.image, class: 'btn white black-text', target: '_new' %>
+ <%= link_to 'Open', commission.image, class: 'btn black-text', target: '_new' %>
<%= link_to 'Delete', basil_delete_path(commission),
- class: 'btn white black-text js-remove-image',
+ class: 'btn black-text js-remove-image',
method: 'delete',
remote: true,
data: { confirm: "Are you sure? This can't be undone." } %>
diff --git a/app/views/content/form/images/_gallery.html.erb b/app/views/content/form/images/_gallery.html.erb
index 5160c36e..afa35711 100644
--- a/app/views/content/form/images/_gallery.html.erb
+++ b/app/views/content/form/images/_gallery.html.erb
@@ -25,7 +25,7 @@
<% all_images.each do |image_item| %>
-
+
<% if image_item[:type] == 'regular' %>
<%= link_to image_item[:data].src(:original), target: "_blank" do %>
<%= image_tag image_item[:data].src(:medium), class: 'gallery-preview-image' %>
@@ -44,7 +44,7 @@
<% else %>
-
+
No images have been added to this <%= content.class.name.downcase %> yet.
<% end %>
@@ -53,8 +53,8 @@
-
View Full Gallery Experience
-
Check out our newly designed gallery page with larger images, lightbox viewing, and an overview section – perfect for sharing with others!
+
View Full Gallery Experience
+
Check out our newly designed gallery page with larger images, lightbox viewing, and an overview section – perfect for sharing with others!
<%= link_to send("gallery_#{content.class.name.downcase}_path", content), class: "btn-large waves-effect waves-light #{content.class.color}" do %>
@@ -81,6 +81,7 @@
margin-bottom: 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
+ background-color: white;
}
.gallery-preview-card:hover {
diff --git a/app/views/content/gallery.html.erb b/app/views/content/gallery.html.erb
index 9de885d2..2529f01d 100644
--- a/app/views/content/gallery.html.erb
+++ b/app/views/content/gallery.html.erb
@@ -520,6 +520,11 @@
.gallery-container {
padding: 50px 0;
background-color: #f9f9f9;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .gallery-container {
+ background-color: #202123;
}
.gallery-grid {
@@ -542,11 +547,16 @@
border-radius: 12px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
- transition: transform 0.3s ease, box-shadow 0.3s ease;
+ transition: transform 0.3s ease, box-shadow 0.3s ease, background-color 1s ease;
cursor: pointer;
height: 100%;
}
+ body.dark .gallery-card {
+ background: rgba(255, 255, 255, 0.1);
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
+ }
+
.gallery-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
@@ -565,6 +575,11 @@
display: flex;
justify-content: space-between;
align-items: center;
+ transition: border-color 1s ease;
+ }
+
+ body.dark .gallery-card-info {
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.image-date {
@@ -572,6 +587,11 @@
align-items: center;
color: #757575;
font-size: 0.9rem;
+ transition: color 1s ease;
+ }
+
+ body.dark .image-date {
+ color: #aaa;
}
.image-date i {
@@ -643,11 +663,21 @@
align-items: center;
justify-content: center;
margin: 0 auto 30px;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .empty-icon {
+ background-color: rgba(255, 255, 255, 0.1);
}
.empty-icon i {
font-size: 50px;
color: #9e9e9e;
+ transition: color 1s ease;
+ }
+
+ body.dark .empty-icon i {
+ color: #aaa;
}
.empty-gallery h2 {
@@ -655,22 +685,42 @@
margin-bottom: 15px;
font-weight: 300;
color: #424242;
+ transition: color 1s ease;
+ }
+
+ body.dark .empty-gallery h2 {
+ color: #fff;
}
.empty-gallery p {
margin-bottom: 30px;
color: #757575;
+ transition: color 1s ease;
+ }
+
+ body.dark .empty-gallery p {
+ color: #aaa;
}
/* ===== OVERVIEW SECTION ===== */
.overview-section {
background-color: white;
padding-bottom: 80px;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .overview-section {
+ background-color: #2D2D31;
}
.overview-background {
background-color: #ffffff;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23<%= @content.class.hex_color.sub('#', '') %>' fill-opacity='0.03'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+ transition: background-color 1s ease;
+ }
+
+ body.dark .overview-background {
+ background-color: #2D2D31;
}
.overview-header {
@@ -696,6 +746,11 @@
font-weight: 300;
margin: 0 0 15px;
color: #212121;
+ transition: color 1s ease;
+ }
+
+ body.dark .overview-title {
+ color: #fff;
}
.overview-subtitle {
@@ -724,6 +779,12 @@
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
+ transition: border-color 1s ease, box-shadow 1s ease;
+ }
+
+ body.dark .overview-fields {
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 2px 12px rgba(0,0,0,0.2);
}
.overview-field {
@@ -739,6 +800,11 @@
right: 25px;
height: 1px;
background: #f5f5f5;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .overview-field:not(:last-child)::after {
+ background: rgba(255, 255, 255, 0.1);
}
.field-label {
@@ -748,11 +814,21 @@
font-weight: 500;
color: #9e9e9e;
margin-bottom: 8px;
+ transition: color 1s ease;
+ }
+
+ body.dark .field-label {
+ color: #aaa;
}
.field-value {
color: #212121;
line-height: 1.6;
+ transition: color 1s ease;
+ }
+
+ body.dark .field-value {
+ color: #ddd;
}
.field-value p {
@@ -769,6 +845,12 @@
background: #f9f9f9;
border-radius: 10px;
color: #757575;
+ transition: background-color 1s ease, color 1s ease;
+ }
+
+ body.dark .no-overview-message {
+ background: rgba(255, 255, 255, 0.05);
+ color: #aaa;
}
.overview-cta {
@@ -780,6 +862,11 @@
.related-section {
background: #f5f5f5;
padding: 70px 0;
+ transition: background-color 1s ease;
+ }
+
+ body.dark .related-section {
+ background: #202123;
}
.related-header {
@@ -794,6 +881,11 @@
display: flex;
align-items: center;
justify-content: center;
+ transition: color 1s ease;
+ }
+
+ body.dark .related-title {
+ color: #fff;
}
.related-title i {
diff --git a/app/views/notice_dismissal/messages/_24.html.erb b/app/views/notice_dismissal/messages/_24.html.erb
index 7ae5f27a..74e02901 100644
--- a/app/views/notice_dismissal/messages/_24.html.erb
+++ b/app/views/notice_dismissal/messages/_24.html.erb
@@ -11,7 +11,7 @@
Participating in Art Fight 2025?
-
+
Click to add the tag below and help others find your <%= page.class.name.downcase %>!
@@ -19,7 +19,7 @@
<% end %>
-
+
lock
Remember to set your <%= page.class.name.downcase %> to public in the privacy toggle if you want others to see it. All pages are private by default.
diff --git a/app/views/users/profile/_tags.html.erb b/app/views/users/profile/_tags.html.erb
index c73d6b95..15ac1c1f 100644
--- a/app/views/users/profile/_tags.html.erb
+++ b/app/views/users/profile/_tags.html.erb
@@ -7,7 +7,7 @@
<% @popular_tags.each do |tag| %>
- <%= link_to user_tag_path(username: @user.username, tag_slug: tag.slug) do %>
+ <%= link_to @user.username.present? ? user_tag_path(username: @user.username, tag_slug: tag.slug) : user_id_tag_path(id: @user.id, tag_slug: tag.slug) do %>
<% end %>
<% end %>
diff --git a/app/views/users/tag.html.erb b/app/views/users/tag.html.erb
index b75d32f8..a51fb73d 100644
--- a/app/views/users/tag.html.erb
+++ b/app/views/users/tag.html.erb
@@ -38,7 +38,7 @@
- <%= link_to profile_by_username_path(username: @user.username), class: "white-text" do %>
+ <%= link_to @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user), class: "white-text" do %>
<%= @user.display_name %>
@@ -65,10 +65,10 @@
category
- <%= pluralize(content_types_count, 'category') %>
+ <%= pluralize(content_types_count, 'page type') %>
- <%= link_to profile_by_username_path(username: @user.username), class: "chip grey lighten-4" do %>
+ <%= link_to @user.username.present? ? profile_by_username_path(username: @user.username) : user_path(@user), class: "chip grey lighten-4" do %>
person
View profile
<% end %>
@@ -147,7 +147,7 @@
<% if tag.tag == @tag.tag %>
<% else %>
- <%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
+ <%= link_to content.user.username.present? ? user_tag_path(username: content.user.username, tag_slug: tag.slug) : user_id_tag_path(id: content.user.id, tag_slug: tag.slug) do %>
<% end %>
<% end %>
@@ -183,7 +183,7 @@
<% if tag.tag == @tag.tag %>
<% else %>
- <%= link_to user_tag_path(username: content.user.username, tag_slug: tag.slug) do %>
+ <%= link_to content.user.username.present? ? user_tag_path(username: content.user.username, tag_slug: tag.slug) : user_id_tag_path(id: content.user.id, tag_slug: tag.slug) do %>
<% end %>
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 74464f01..5ce2d396 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -110,6 +110,9 @@ Rails.application.routes.draw do
get '/@:username/followers', to: 'users#followers'
get '/@:username/following', to: 'users#following'
get '/@:username/tag/:tag_slug', to: 'users#tag', as: :user_tag
+
+ # ID-based alternative routes for users without usernames
+ get '/users/:id/tag/:tag_slug', to: 'users#tag', as: :user_id_tag
resources :documents do
# Document Analysis routes
diff --git a/test/controllers/to_write/notice_dismissal_controller_test.rb b/test/controllers/to_write/notice_dismissal_controller_test.rb
index 90a4f1a6..58e60b2a 100644
--- a/test/controllers/to_write/notice_dismissal_controller_test.rb
+++ b/test/controllers/to_write/notice_dismissal_controller_test.rb
@@ -1,9 +1,8 @@
require 'test_helper'
class NoticeDismissalControllerTest < ActionDispatch::IntegrationTest
- # test "should get dismiss" do
- # get notice_dismissal_dismiss_url
- # assert_response :success
- # end
-
+ # Tests are disabled until properly implemented
+ def test_should_get_root_url
+ skip("Test not implemented yet")
+ end
end
diff --git a/test/fixtures/user.yml b/test/fixtures/user.yml
index 3c6bf7a1..53fda7b3 100644
--- a/test/fixtures/user.yml
+++ b/test/fixtures/user.yml
@@ -1,11 +1,11 @@
starter:
- id: 1
+ id: 3
name: Starter User
email: test@test.test
selected_billing_plan_id: 1
premium:
- id: 2
+ id: 4
name: Premium User
email: test@test.test
selected_billing_plan_id: 3
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 658aea2e..15fea2b3 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -9,7 +9,7 @@ require "rails/test_help"
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
- parallelize(workers: :number_of_processors)
+ # parallelize(workers: :number_of_processors) # Temporarily disabled to fix test issues
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all