Compare commits
1 Commits
ef3f05661e
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| b580431b12 |
@@ -687,8 +687,8 @@ export default class extends Controller {
|
|||||||
// Show preview
|
// Show preview
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const previewContainer = document.getElementById('upload-preview')
|
const previewContainer = document.getElementById('image-preview')
|
||||||
const previewImg = document.getElementById('upload-preview-img')
|
const previewImg = document.getElementById('preview-img')
|
||||||
|
|
||||||
if (previewContainer && previewImg) {
|
if (previewContainer && previewImg) {
|
||||||
previewImg.src = e.target.result
|
previewImg.src = e.target.result
|
||||||
@@ -697,54 +697,4 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview image from URL
|
|
||||||
previewImageUrl(event) {
|
|
||||||
const url = event.target.value.trim()
|
|
||||||
const previewContainer = document.getElementById('url-preview')
|
|
||||||
const previewImg = document.getElementById('url-preview-img')
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.classList.add('hidden')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic URL validation
|
|
||||||
if (!this.isValidImageUrl(url)) {
|
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.classList.add('hidden')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show preview with error handling
|
|
||||||
if (previewImg) {
|
|
||||||
previewImg.onload = () => {
|
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.classList.remove('hidden')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previewImg.onerror = () => {
|
|
||||||
if (previewContainer) {
|
|
||||||
previewContainer.classList.add('hidden')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previewImg.src = url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate image URL format
|
|
||||||
isValidImageUrl(url) {
|
|
||||||
try {
|
|
||||||
new URL(url)
|
|
||||||
// Check if it looks like an image URL
|
|
||||||
return /\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i.test(url)
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ class Event < ApplicationRecord
|
|||||||
has_many :promotion_codes
|
has_many :promotion_codes
|
||||||
has_one_attached :image
|
has_one_attached :image
|
||||||
|
|
||||||
# === Virtual attribute for backward compatibility with image URLs ===
|
|
||||||
attr_accessor :image_url
|
|
||||||
|
|
||||||
|
|
||||||
# === Callbacks ===
|
# === Callbacks ===
|
||||||
before_validation :geocode_address, if: :should_geocode_address?
|
before_validation :geocode_address, if: :should_geocode_address?
|
||||||
@@ -37,11 +34,8 @@ class Event < ApplicationRecord
|
|||||||
validates :slug, presence: true, length: { minimum: 3, maximum: 100 }
|
validates :slug, presence: true, length: { minimum: 3, maximum: 100 }
|
||||||
validates :description, presence: true, length: { minimum: 10, maximum: 2000 }
|
validates :description, presence: true, length: { minimum: 10, maximum: 2000 }
|
||||||
validates :state, presence: true, inclusion: { in: states.keys }
|
validates :state, presence: true, inclusion: { in: states.keys }
|
||||||
|
|
||||||
# Image validation - handles both attachments and URLs
|
|
||||||
validate :image_format, if: -> { image.attached? }
|
validate :image_format, if: -> { image.attached? }
|
||||||
validate :image_size, if: -> { image.attached? }
|
validate :image_size, if: -> { image.attached? }
|
||||||
validate :image_url_format, if: -> { image_url.present? && !image.attached? }
|
|
||||||
|
|
||||||
# Venue information
|
# Venue information
|
||||||
validates :venue_name, presence: true, length: { maximum: 100 }
|
validates :venue_name, presence: true, length: { maximum: 100 }
|
||||||
@@ -67,36 +61,17 @@ class Event < ApplicationRecord
|
|||||||
|
|
||||||
# === Instance Methods ===
|
# === Instance Methods ===
|
||||||
|
|
||||||
# Get image for display - handles both uploaded files and URLs
|
# Get image variants for different display sizes
|
||||||
def event_image_variant(size = :medium)
|
def event_image_variant(size = :medium)
|
||||||
if image.attached?
|
case size
|
||||||
case size
|
when :large
|
||||||
when :large
|
image.variant(resize_to_limit: [1200, 630])
|
||||||
image.variant(resize_to_limit: [1200, 630])
|
when :medium
|
||||||
when :medium
|
image.variant(resize_to_limit: [800, 450])
|
||||||
image.variant(resize_to_limit: [800, 450])
|
when :small
|
||||||
when :small
|
image.variant(resize_to_limit: [400, 225])
|
||||||
image.variant(resize_to_limit: [400, 225])
|
|
||||||
else
|
|
||||||
image
|
|
||||||
end
|
|
||||||
else
|
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
|
image
|
||||||
else
|
|
||||||
image_url
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -192,15 +167,6 @@ class Event < ApplicationRecord
|
|||||||
end
|
end
|
||||||
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
|
private
|
||||||
|
|
||||||
# Determine if we should perform server-side geocoding
|
# Determine if we should perform server-side geocoding
|
||||||
|
|||||||
@@ -194,14 +194,8 @@ class Order < ApplicationRecord
|
|||||||
|
|
||||||
# Prevent duplicate promotion codes on the same order
|
# Prevent duplicate promotion codes on the same order
|
||||||
def no_duplicate_promotion_codes
|
def no_duplicate_promotion_codes
|
||||||
return if promotion_codes.empty?
|
promotion_code_ids = promotion_codes.map(&:id)
|
||||||
|
if promotion_code_ids.size != promotion_code_ids.uniq.size
|
||||||
# Use distinct to avoid association loading issues
|
|
||||||
unique_codes = promotion_codes.distinct
|
|
||||||
code_counts = unique_codes.group_by(&:code).transform_values(&:count)
|
|
||||||
duplicates = code_counts.select { |_, count| count > 1 }
|
|
||||||
|
|
||||||
if duplicates.any?
|
|
||||||
errors.add(:promotion_codes, "ne peuvent pas contenir de codes en double")
|
errors.add(:promotion_codes, "ne peuvent pas contenir de codes en double")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
<%= link_to event_path(event.slug, event), class: "group block p-4 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 hover:shadow-md transition-all duration-200" do %>
|
<%= link_to event_path(event.slug, event), class: "group block p-4 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 hover:shadow-md transition-all duration-200" do %>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
<div class="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
||||||
<% if event.has_image? %>
|
<%= image_tag event.event_image_variant(:small), alt: event.name, class: "w-full h-full object-cover" if event.image.attached? %>
|
||||||
<% if event.image.attached? %>
|
|
||||||
<%= image_tag event.event_image_variant(:small), alt: event.name, class: "w-full h-full object-cover" %>
|
|
||||||
<% else %>
|
|
||||||
<%= image_tag event.image_url, alt: event.name, class: "w-full h-full object-cover" %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors duration-200">
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors duration-200">
|
||||||
|
|||||||
@@ -22,13 +22,9 @@
|
|||||||
<% @events.each do |event| %>
|
<% @events.each do |event| %>
|
||||||
<article class="group bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden transform hover:-translate-y-1">
|
<article class="group bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden transform hover:-translate-y-1">
|
||||||
<%= link_to event_path(event.slug, event), class: "block" do %>
|
<%= link_to event_path(event.slug, event), class: "block" do %>
|
||||||
<% if event.has_image? %>
|
<% if event.image.attached? %>
|
||||||
<div class="relative overflow-hidden aspect-[4/3]">
|
<div class="relative overflow-hidden aspect-[4/3]">
|
||||||
<% if event.image.attached? %>
|
<%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
||||||
<%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
|
||||||
<% else %>
|
|
||||||
<%= image_tag event.image_url, alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
|
||||||
<% end %>
|
|
||||||
<!-- Event featured badge -->
|
<!-- Event featured badge -->
|
||||||
<% if event.featured? %>
|
<% if event.featured? %>
|
||||||
<div class="absolute top-4 left-4">
|
<div class="absolute top-4 left-4">
|
||||||
|
|||||||
@@ -10,13 +10,9 @@
|
|||||||
<!-- Event main wrapper -->
|
<!-- Event main wrapper -->
|
||||||
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
|
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
|
||||||
<!-- Event Header with Image -->
|
<!-- Event Header with Image -->
|
||||||
<% if @event.has_image? %>
|
<% if @event.image.attached? %>
|
||||||
<div class="relative h-96">
|
<div class="relative h-96">
|
||||||
<% if @event.image.attached? %>
|
<%= image_tag @event.event_image_variant(:large), class: "w-full h-full object-cover" %>
|
||||||
<%= image_tag @event.event_image_variant(:large), class: "w-full h-full object-cover" %>
|
|
||||||
<% else %>
|
|
||||||
<%= image_tag @event.image_url, class: "w-full h-full object-cover", alt: @event.name %>
|
|
||||||
<% end %>
|
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/70 to-transparent"></div>
|
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/70 to-transparent"></div>
|
||||||
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-8">
|
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-8">
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
|
|||||||
@@ -89,12 +89,8 @@
|
|||||||
<div class="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden">
|
<div class="bg-white rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 overflow-hidden">
|
||||||
<!-- Event Image -->
|
<!-- Event Image -->
|
||||||
<div class="relative overflow-hidden aspect-[4/3]">
|
<div class="relative overflow-hidden aspect-[4/3]">
|
||||||
<% if event.has_image? %>
|
<% if event.image.attached? %>
|
||||||
<% if event.image.attached? %>
|
<%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
||||||
<%= image_tag event.event_image_variant(:medium), alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
|
||||||
<% else %>
|
|
||||||
<%= image_tag event.image_url, alt: event.name, class: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" %>
|
|
||||||
<% end %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="w-full h-full bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center">
|
<div class="w-full h-full bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center">
|
||||||
<i data-lucide="calendar" class="w-16 h-16 text-white"></i>
|
<i data-lucide="calendar" class="w-16 h-16 text-white"></i>
|
||||||
|
|||||||
@@ -68,37 +68,16 @@
|
|||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %>
|
<%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %>
|
||||||
|
<div class="space-y-4">
|
||||||
<!-- Image type selection tabs -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="border-b border-gray-200">
|
|
||||||
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
|
|
||||||
<button type="button" onclick="switchImageTab('upload')" id="upload-tab" class="tab-button active border-purple-500 text-purple-600 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
|
||||||
<i data-lucide="upload" class="w-4 h-4 inline mr-2"></i>
|
|
||||||
Télécharger un fichier
|
|
||||||
</button>
|
|
||||||
<button type="button" onclick="switchImageTab('url')" id="url-tab" class="tab-button border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
|
||||||
<i data-lucide="link" class="w-4 h-4 inline mr-2"></i>
|
|
||||||
Utiliser une URL
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Upload tab content -->
|
|
||||||
<div id="upload-content" class="tab-content space-y-4">
|
|
||||||
<!-- Current image preview -->
|
<!-- Current image preview -->
|
||||||
<% if @event.image.attached? %>
|
<% if @event.image.attached? %>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %>
|
<%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %>
|
||||||
<div class="absolute top-2 right-2">
|
<div class="absolute top-2 right-2">
|
||||||
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('upload-preview').classList.add('hidden');" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
<button type="button" onclick="this.closest('div').querySelector('input[type=file]').click()" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
||||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-2 left-2 bg-black bg-opacity-50 text-white px-2 py-1 rounded text-xs">
|
|
||||||
Image actuelle
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -114,57 +93,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image preview container -->
|
<!-- Image preview container -->
|
||||||
<div id="upload-preview" class="hidden">
|
<div id="image-preview" class="hidden">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img id="upload-preview-img" src="" alt="Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
<img id="preview-img" src="" alt="Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
||||||
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('upload-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('image-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
||||||
<i data-lucide="x" class="w-4 h-4"></i>
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="absolute bottom-2 left-2 bg-purple-600 text-white px-2 py-1 rounded text-xs">
|
|
||||||
Nouvelle image
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- URL tab content -->
|
|
||||||
<div id="url-content" class="tab-content space-y-4 hidden">
|
|
||||||
<!-- Current URL image preview -->
|
|
||||||
<% if @event.image_url.present? && !@event.image.attached? %>
|
|
||||||
<div class="relative">
|
|
||||||
<%= image_tag @event.image_url, class: "w-full h-48 object-cover rounded-lg border border-gray-200", alt: "Current URL image" %>
|
|
||||||
<div class="absolute top-2 right-2">
|
|
||||||
<button type="button" onclick="document.getElementById('event_image_url').value = ''; document.getElementById('url-preview').classList.add('hidden');" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
|
||||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="absolute bottom-2 left-2 bg-black bg-opacity-50 text-white px-2 py-1 rounded text-xs">
|
|
||||||
URL actuelle
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<!-- URL input field -->
|
|
||||||
<div class="relative">
|
|
||||||
<%= form.text_field :image_url, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", placeholder: "https://example.com/image.jpg", value: @event.image_url, data: { action: "input->event-form#previewImageUrl" } %>
|
|
||||||
<div class="mt-1 text-sm text-gray-500">
|
|
||||||
Entrez l'URL d'une image (JPG, PNG, GIF, WebP)
|
|
||||||
<% if @event.image_url.present? %>
|
|
||||||
<br>Laissez vide pour conserver l'URL actuelle
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- URL preview container -->
|
|
||||||
<div id="url-preview" class="hidden">
|
|
||||||
<div class="relative">
|
|
||||||
<img id="url-preview-img" src="" alt="URL Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
|
||||||
<button type="button" onclick="document.getElementById('event_image_url').value = ''; document.getElementById('url-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
|
||||||
<i data-lucide="x" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
<div class="absolute bottom-2 left-2 bg-purple-600 text-white px-2 py-1 rounded text-xs">
|
|
||||||
Nouvelle URL
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -302,26 +236,3 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function switchImageTab(tab) {
|
|
||||||
// Hide all tab contents
|
|
||||||
document.querySelectorAll('.tab-content').forEach(content => {
|
|
||||||
content.classList.add('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove active class from all tabs
|
|
||||||
document.querySelectorAll('.tab-button').forEach(button => {
|
|
||||||
button.classList.remove('active', 'border-purple-500', 'text-purple-600');
|
|
||||||
button.classList.add('border-transparent', 'text-gray-500');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show selected tab content
|
|
||||||
document.getElementById(tab + '-content').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Add active class to selected tab
|
|
||||||
const activeTab = document.getElementById(tab + '-tab');
|
|
||||||
activeTab.classList.add('active', 'border-purple-500', 'text-purple-600');
|
|
||||||
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -61,31 +61,13 @@
|
|||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %>
|
<%= form.label :image, "Image de couverture", class: "block text-sm font-medium text-gray-700 mb-2" %>
|
||||||
|
<div class="space-y-4">
|
||||||
<!-- Image type selection tabs -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="border-b border-gray-200">
|
|
||||||
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
|
|
||||||
<button type="button" onclick="switchImageTab('upload')" id="upload-tab" class="tab-button active border-purple-500 text-purple-600 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
|
||||||
<i data-lucide="upload" class="w-4 h-4 inline mr-2"></i>
|
|
||||||
Télécharger un fichier
|
|
||||||
</button>
|
|
||||||
<button type="button" onclick="switchImageTab('url')" id="url-tab" class="tab-button border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm">
|
|
||||||
<i data-lucide="link" class="w-4 h-4 inline mr-2"></i>
|
|
||||||
Utiliser une URL
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Upload tab content -->
|
|
||||||
<div id="upload-content" class="tab-content space-y-4">
|
|
||||||
<!-- Current image preview (for edit mode) -->
|
<!-- Current image preview (for edit mode) -->
|
||||||
<% if @event.image.attached? %>
|
<% if @event.image.attached? %>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %>
|
<%= image_tag @event.image.variant(resize_to_limit: [400, 225]), class: "w-full h-48 object-cover rounded-lg border border-gray-200" %>
|
||||||
<div class="absolute top-2 right-2">
|
<div class="absolute top-2 right-2">
|
||||||
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('upload-preview').classList.add('hidden');" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
<button type="button" onclick="this.closest('div').previousElementSibling.querySelector('input[type=file]').click()" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
||||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,43 +83,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image preview container -->
|
<!-- Image preview container -->
|
||||||
<div id="upload-preview" class="hidden">
|
<div id="image-preview" class="hidden">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img id="upload-preview-img" src="" alt="Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
<img id="preview-img" src="" alt="Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
||||||
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('upload-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
<button type="button" onclick="document.getElementById('event_image').value = ''; document.getElementById('image-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
||||||
<i data-lucide="x" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- URL tab content -->
|
|
||||||
<div id="url-content" class="tab-content space-y-4 hidden">
|
|
||||||
<!-- Current URL image preview -->
|
|
||||||
<% if @event.image_url.present? && !@event.image.attached? %>
|
|
||||||
<div class="relative">
|
|
||||||
<%= image_tag @event.image_url, class: "w-full h-48 object-cover rounded-lg border border-gray-200", alt: "Current image" %>
|
|
||||||
<div class="absolute top-2 right-2">
|
|
||||||
<button type="button" onclick="document.getElementById('event_image_url').value = ''; document.getElementById('url-preview').classList.add('hidden');" class="bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
|
||||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<!-- URL input field -->
|
|
||||||
<div class="relative">
|
|
||||||
<%= form.text_field :image_url, class: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent", placeholder: "https://example.com/image.jpg", data: { action: "input->event-form#previewImageUrl" } %>
|
|
||||||
<div class="mt-1 text-sm text-gray-500">
|
|
||||||
Entrez l'URL d'une image (JPG, PNG, GIF, WebP)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- URL preview container -->
|
|
||||||
<div id="url-preview" class="hidden">
|
|
||||||
<div class="relative">
|
|
||||||
<img id="url-preview-img" src="" alt="URL Preview" class="w-full h-48 object-cover rounded-lg border border-gray-200">
|
|
||||||
<button type="button" onclick="document.getElementById('event_image_url').value = ''; document.getElementById('url-preview').classList.add('hidden');" class="absolute top-2 right-2 bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors">
|
|
||||||
<i data-lucide="x" class="w-4 h-4"></i>
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,26 +192,3 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function switchImageTab(tab) {
|
|
||||||
// Hide all tab contents
|
|
||||||
document.querySelectorAll('.tab-content').forEach(content => {
|
|
||||||
content.classList.add('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove active class from all tabs
|
|
||||||
document.querySelectorAll('.tab-button').forEach(button => {
|
|
||||||
button.classList.remove('active', 'border-purple-500', 'text-purple-600');
|
|
||||||
button.classList.add('border-transparent', 'text-gray-500');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show selected tab content
|
|
||||||
document.getElementById(tab + '-content').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Add active class to selected tab
|
|
||||||
const activeTab = document.getElementById(tab + '-tab');
|
|
||||||
activeTab.classList.add('active', 'border-purple-500', 'text-purple-600');
|
|
||||||
activeTab.classList.remove('border-transparent', 'text-gray-500');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -174,13 +174,9 @@
|
|||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
<div class="lg:col-span-2 space-y-6 lg:space-y-8">
|
<div class="lg:col-span-2 space-y-6 lg:space-y-8">
|
||||||
<!-- Event image -->
|
<!-- Event image -->
|
||||||
<% if @event.has_image? %>
|
<% if @event.image.attached? %>
|
||||||
<div class="aspect-video bg-gray-100 rounded-2xl overflow-hidden">
|
<div class="aspect-video bg-gray-100 rounded-2xl overflow-hidden">
|
||||||
<% if @event.image.attached? %>
|
<%= image_tag @event.event_image_variant(:large), alt: @event.name, class: "w-full h-full object-cover" %>
|
||||||
<%= image_tag @event.event_image_variant(:large), alt: @event.name, class: "w-full h-full object-cover" %>
|
|
||||||
<% else %>
|
|
||||||
<%= image_tag @event.image_url, alt: @event.name, class: "w-full h-full object-cover" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
1
db/schema.rb
generated
1
db/schema.rb
generated
@@ -56,7 +56,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_29_222616) do
|
|||||||
t.boolean "allow_booking_during_event", default: false, null: false
|
t.boolean "allow_booking_during_event", default: false, null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "image_url"
|
|
||||||
t.index ["featured"], name: "index_events_on_featured"
|
t.index ["featured"], name: "index_events_on_featured"
|
||||||
t.index ["latitude", "longitude"], name: "index_events_on_latitude_and_longitude"
|
t.index ["latitude", "longitude"], name: "index_events_on_latitude_and_longitude"
|
||||||
t.index ["state"], name: "index_events_on_state"
|
t.index ["state"], name: "index_events_on_state"
|
||||||
|
|||||||
14
db/seeds.rb
14
db/seeds.rb
@@ -44,7 +44,7 @@ events_data = [
|
|||||||
start_time: 1.day.from_now,
|
start_time: 1.day.from_now,
|
||||||
end_time: 1.day.from_now + 6.hours,
|
end_time: 1.day.from_now + 6.hours,
|
||||||
featured: true,
|
featured: true,
|
||||||
image_url: "https://fastly.picsum.photos/id/407/300/200.jpg?hmac=9EhoXMZ1QdwJue90vzxcjBg2YzsZsAWCjJ7oxOhtcU0",
|
image: "https://fastly.picsum.photos/id/407/300/200.jpg?hmac=9EhoXMZ1QdwJue90vzxcjBg2YzsZsAWCjJ7oxOhtcU0",
|
||||||
user: users.first
|
user: users.first
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ events_data = [
|
|||||||
start_time: 3.days.from_now,
|
start_time: 3.days.from_now,
|
||||||
end_time: 3.days.from_now + 4.hours,
|
end_time: 3.days.from_now + 4.hours,
|
||||||
featured: true,
|
featured: true,
|
||||||
image_url: "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
image: "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||||
user: users.second
|
user: users.second
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ events_data = [
|
|||||||
start_time: 1.week.from_now,
|
start_time: 1.week.from_now,
|
||||||
end_time: 1.week.from_now + 8.hours,
|
end_time: 1.week.from_now + 8.hours,
|
||||||
featured: false,
|
featured: false,
|
||||||
image_url: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
image: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
||||||
user: users.third
|
user: users.third
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -147,7 +147,7 @@ belle_epoque_event = Event.find_or_create_by!(name: "LA BELLE ÉPOQUE PAR SISLEY
|
|||||||
e.start_time = 3.days.from_now
|
e.start_time = 3.days.from_now
|
||||||
e.end_time = 3.days.from_now + 8.hours
|
e.end_time = 3.days.from_now + 8.hours
|
||||||
e.featured = false
|
e.featured = false
|
||||||
e.image_url = "https://data.bizouk.com/cache1/events/images/10/78/87/b801a9a43266b4cc54bdda73bf34eec8_700_800_auto_97.jpg"
|
e.image = "https://data.bizouk.com/cache1/events/images/10/78/87/b801a9a43266b4cc54bdda73bf34eec8_700_800_auto_97.jpg"
|
||||||
e.user = promoter
|
e.user = promoter
|
||||||
e.allow_booking_during_event = true
|
e.allow_booking_during_event = true
|
||||||
end
|
end
|
||||||
@@ -201,7 +201,7 @@ konpa_event = Event.find_or_create_by!(name: "Konpa With Bev - Cours De Konpa Go
|
|||||||
e.start_time = Time.parse("2025-10-03 19:00:00")
|
e.start_time = Time.parse("2025-10-03 19:00:00")
|
||||||
e.end_time = Time.parse("2025-10-03 23:00:00")
|
e.end_time = Time.parse("2025-10-03 23:00:00")
|
||||||
e.featured = false
|
e.featured = false
|
||||||
e.image_url = "https://data.bizouk.com/cache1/events/images/10/79/61/081f38b583ac651f3a0930c5d8f13458_800_600_auto_97.png"
|
e.image = "https://data.bizouk.com/cache1/events/images/10/79/61/081f38b583ac651f3a0930c5d8f13458_800_600_auto_97.png"
|
||||||
e.user = promoter
|
e.user = promoter
|
||||||
e.state = :published
|
e.state = :published
|
||||||
end
|
end
|
||||||
@@ -216,7 +216,7 @@ caribbean_groove_event = Event.find_or_create_by!(name: "La Plus Grosse Soirée
|
|||||||
e.start_time = Time.parse("2025-10-03 23:00:00")
|
e.start_time = Time.parse("2025-10-03 23:00:00")
|
||||||
e.end_time = Time.parse("2025-10-04 05:00:00")
|
e.end_time = Time.parse("2025-10-04 05:00:00")
|
||||||
e.featured = false
|
e.featured = false
|
||||||
e.image_url = "https://data.bizouk.com/cache1/events/images/10/83/15/fa5d43f0b1998f691181cfda8fe35213_800_600_auto_97.png"
|
e.image = "https://data.bizouk.com/cache1/events/images/10/83/15/fa5d43f0b1998f691181cfda8fe35213_800_600_auto_97.png"
|
||||||
e.user = promoter
|
e.user = promoter
|
||||||
e.state = :published
|
e.state = :published
|
||||||
end
|
end
|
||||||
@@ -231,7 +231,7 @@ belle_epoque_october_event = Event.find_or_create_by!(name: "LA BELLE ÉPOQUE PA
|
|||||||
e.start_time = Time.parse("2025-10-04 18:00:00")
|
e.start_time = Time.parse("2025-10-04 18:00:00")
|
||||||
e.end_time = Time.parse("2025-10-05 02:00:00")
|
e.end_time = Time.parse("2025-10-05 02:00:00")
|
||||||
e.featured = false
|
e.featured = false
|
||||||
e.image_url = "https://data.bizouk.com/cache1/events/images/10/92/72/351e61b55603a4d142b43486216457c1_800_600_auto_97.jpg"
|
e.image = "https://data.bizouk.com/cache1/events/images/10/92/72/351e61b55603a4d142b43486216457c1_800_600_auto_97.jpg"
|
||||||
e.user = promoter
|
e.user = promoter
|
||||||
e.state = :published
|
e.state = :published
|
||||||
e.allow_booking_during_event = true
|
e.allow_booking_during_event = true
|
||||||
|
|||||||
Reference in New Issue
Block a user