feat: Complete hybrid image upload system with URL compatibility

- Add hybrid image system supporting both file uploads and URL images
- Implement Active Storage for file uploads while preserving existing URL functionality
- Update Event model with both has_one_attached :image and image_url virtual attribute
- Create tabbed interface in event forms for upload/URL selection
- Add JavaScript preview functionality for both upload and URL inputs
- Fix promotion code validation issue in tests using distinct() to prevent duplicates
- Update all views to use hybrid display methods prioritizing uploads over URLs
- Update seeds file to use image_url attribute for compatibility
- Ensure backward compatibility with existing events using URL images

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
kbe
2025-09-30 01:06:12 +02:00
parent d85996a1bb
commit ef3f05661e
12 changed files with 316 additions and 40 deletions

View File

@@ -24,7 +24,10 @@ class Event < ApplicationRecord
has_many :promotion_codes
has_one_attached :image
# === Virtual attribute for backward compatibility with image URLs ===
attr_accessor :image_url
# === Callbacks ===
before_validation :geocode_address, if: :should_geocode_address?
@@ -34,8 +37,11 @@ class Event < ApplicationRecord
validates :slug, presence: true, length: { minimum: 3, maximum: 100 }
validates :description, presence: true, length: { minimum: 10, maximum: 2000 }
validates :state, presence: true, inclusion: { in: states.keys }
# Image validation - handles both attachments and URLs
validate :image_format, if: -> { image.attached? }
validate :image_size, if: -> { image.attached? }
validate :image_url_format, if: -> { image_url.present? && !image.attached? }
# Venue information
validates :venue_name, presence: true, length: { maximum: 100 }
@@ -61,17 +67,36 @@ class Event < ApplicationRecord
# === Instance Methods ===
# Get image variants for different display sizes
# Get image for display - handles both uploaded files and URLs
def event_image_variant(size = :medium)
case size
when :large
image.variant(resize_to_limit: [1200, 630])
when :medium
image.variant(resize_to_limit: [800, 450])
when :small
image.variant(resize_to_limit: [400, 225])
if image.attached?
case size
when :large
image.variant(resize_to_limit: [1200, 630])
when :medium
image.variant(resize_to_limit: [800, 450])
when :small
image.variant(resize_to_limit: [400, 225])
else
image
end
else
# Fallback to URL-based image
image_url.presence
end
end
# Check if event has any image (uploaded or URL)
def has_image?
image.attached? || image_url.present?
end
# Get display image source (uploaded or URL)
def display_image
if image.attached?
image
else
image_url
end
end
@@ -167,6 +192,15 @@ class Event < ApplicationRecord
end
end
# Validate image URL format
def image_url_format
return unless image_url.present?
unless image_url.match?(/\Ahttps?:\/\/.+\.(jpg|jpeg|png|gif|webp)(\?.*)?\z/i)
errors.add(:image_url, "doit être une URL valide vers une image (JPG, PNG, GIF, WebP)")
end
end
private
# Determine if we should perform server-side geocoding