3 Commits

Author SHA1 Message Date
kbe
60b7bc6aa7 feat: Update from breadcrumb on the current page 2025-09-10 14:31:48 +02:00
kbe
8d2127fce2 feat: Use invoice emitter details from env var 2025-09-10 10:21:32 +02:00
kbe
2fb0e1fdbb Make invoice emitter configurable via environment variables
Add environment variables for invoice company details to allow customization without code changes. Update invoice view and Stripe service to use these configurable values.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-10 10:16:24 +02:00
11 changed files with 79 additions and 80 deletions

View File

@@ -30,6 +30,16 @@ SMTP_AUTHENTICATION=plain
SMTP_ENABLE_STARTTLS=false
# SMTP_STARTTLS=true
# Invoice Emitter Configuration
INVOICE_COMPANY_NAME=AperoNight
INVOICE_COMPANY_ADDRESS_LINE_1=123 Avenue des Événements
INVOICE_COMPANY_ADDRESS_LINE_2=75000 Paris, France
INVOICE_COMPANY_EMAIL=contact@apero-night.fr
INVOICE_COMPANY_PHONE=
INVOICE_COMPANY_WEBSITE=
INVOICE_COMPANY_VAT_NUMBER=
INVOICE_COMPANY_SIRET=
# Application variables
STRIPE_PUBLISHABLE_KEY=pk_test_51S1M7BJWx6G2LLIXYpTvi0hxMpZ4tZSxkmr2Wbp1dQ73MKNp4Tyu4xFJBqLXK5nn4E0nEf2tdgJqEwWZLosO3QGn00kMvjXWGW
STRIPE_SECRET_KEY=sk_test_51S1M7BJWx6G2LLIXK2pdLpRKb9Mgd3sZ30N4ueVjHepgxQKbWgMVJoa4v4ESzHQ6u6zJjO4jUvgLYPU1QLyAiFTN00sGz2ortW

View File

@@ -1,6 +1,6 @@
class OnboardingController < ApplicationController
before_action :authenticate_user!
before_action :redirect_if_onboarding_complete, except: [:complete]
before_action :redirect_if_onboarding_complete, except: [ :complete ]
def index
# Display the onboarding form
@@ -10,7 +10,7 @@ class OnboardingController < ApplicationController
if onboarding_params_valid?
current_user.update!(onboarding_params)
current_user.complete_onboarding!
flash[:notice] = "Bienvenue sur #{Rails.application.config.app_name} ! Votre profil a été configuré avec succès."
redirect_to dashboard_path
else
@@ -26,7 +26,7 @@ class OnboardingController < ApplicationController
end
def onboarding_params_valid?
onboarding_params[:first_name].present? &&
onboarding_params[:first_name].present? &&
onboarding_params[:last_name].present?
end

View File

@@ -9,19 +9,19 @@ class PagesController < ApplicationController
def home
# Featured events for the main grid (6-9 events like Shotgun)
@featured_events = Event.published.featured.includes(:ticket_types).limit(9)
# If no featured events, show latest published events
if @featured_events.empty?
@featured_events = Event.published.includes(:ticket_types).order(created_at: :desc).limit(9)
end
# Upcoming events for additional content
@upcoming_events = Event.published.upcoming.limit(6)
# Site metrics for landing page (with realistic fake data for demo)
@total_events = [Event.published.count, 50].max # At least 50 events for demo
@total_users = [User.count, 2500].max # At least 2500 users for demo
@events_this_month = [Event.published.where(created_at: 1.month.ago..Time.current).count, 12].max # At least 12 this month
@total_events = [ Event.published.count, 50 ].max # At least 50 events for demo
@total_users = [ User.count, 2500 ].max # At least 2500 users for demo
@events_this_month = [ Event.published.where(created_at: 1.month.ago..Time.current).count, 12 ].max # At least 12 this month
@active_cities = 5 # Fixed number for demo
end

View File

@@ -103,7 +103,7 @@ class StripeInvoiceService
name: customer_name,
metadata: {
user_id: @order.user.id,
created_by: "aperonight_system"
created_by: "#{ENV.fetch('INVOICE_COMPANY_NAME', 'aperonight').downcase}_system"
}
})
@@ -133,7 +133,7 @@ class StripeInvoiceService
order_id: @order.id,
user_id: @order.user.id,
event_name: @order.event.name,
created_by: "aperonight_system",
created_by: "#{ENV.fetch('INVOICE_COMPANY_NAME', 'aperonight').downcase}_system",
payment_method: "checkout_session"
},
description: "Invoice for #{@order.event.name} - Order ##{@order.id}",

View File

@@ -31,11 +31,26 @@
<div>
<h3 class="text-lg font-semibold text-gray-900 mb-3">Émis par</h3>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
<h4 class="font-semibold text-purple-900">AperoNight</h4>
<h4 class="font-semibold text-purple-900"><%= ENV.fetch("INVOICE_COMPANY_NAME", "AperoNight") %></h4>
<div class="mt-2 space-y-1 text-sm text-purple-700">
<p>123 Avenue des Événements</p>
<p>75000 Paris, France</p>
<p>contact@apero-night.fr</p>
<% if ENV["INVOICE_COMPANY_ADDRESS_LINE_1"].present? %>
<p><%= ENV["INVOICE_COMPANY_ADDRESS_LINE_1"] %></p>
<% end %>
<% if ENV["INVOICE_COMPANY_ADDRESS_LINE_2"].present? %>
<p><%= ENV["INVOICE_COMPANY_ADDRESS_LINE_2"] %></p>
<% end %>
<% if ENV["INVOICE_COMPANY_EMAIL"].present? %>
<p><%= ENV["INVOICE_COMPANY_EMAIL"] %></p>
<% end %>
<% if ENV["INVOICE_COMPANY_PHONE"].present? %>
<p><%= ENV["INVOICE_COMPANY_PHONE"] %></p>
<% end %>
<% if ENV["INVOICE_COMPANY_VAT_NUMBER"].present? %>
<p>TVA: <%= ENV["INVOICE_COMPANY_VAT_NUMBER"] %></p>
<% end %>
<% if ENV["INVOICE_COMPANY_SIRET"].present? %>
<p>SIRET: <%= ENV["INVOICE_COMPANY_SIRET"] %></p>
<% end %>
</div>
</div>
</div>

View File

@@ -1,37 +1,11 @@
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Breadcrumb -->
<nav class="flex mb-6" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-2 rounded-lg bg-white px-4 py-2 shadow-sm">
<li class="inline-flex items-center">
<%= link_to "Accueil", root_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-purple-600" %>
</li>
<li>
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
<%= link_to "Événements", events_path, class: "ml-1 text-sm font-medium text-gray-700 hover:text-purple-600 md:ml-2" %>
</div>
</li>
<li>
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
<%= link_to @event.name, event_path(@event.slug, @event), class: "ml-1 text-sm font-medium text-gray-700 hover:text-purple-600 md:ml-2" %>
</div>
</li>
<li>
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
<span class="ml-1 text-sm font-medium text-purple-600 md:ml-2">Nouvelle commande</span>
</div>
</li>
</ol>
</nav>
<%= render 'components/breadcrumb', crumbs: [
{ name: 'Accueil', path: root_path },
{ name: 'Événements', path: events_path },
{ name: @event.name, path: event_path(@event.slug, @event) },
{ name: 'Nouvelle commande', path: nil }
] %>
<!-- Page Header -->
<div class="mb-8">

View File

@@ -4,7 +4,7 @@ class ApplicationControllerOnboardingTest < ActionDispatch::IntegrationTest
setup do
@user_without_onboarding = users(:one)
@user_without_onboarding.update!(onboarding_completed: false)
@user_with_onboarding = users(:two)
@user_with_onboarding.update!(onboarding_completed: true, first_name: "John", last_name: "Doe")
end
@@ -54,4 +54,4 @@ class ApplicationControllerOnboardingTest < ActionDispatch::IntegrationTest
get onboarding_path
assert_response :success
end
end
end

View File

@@ -4,7 +4,7 @@ class OnboardingControllerTest < ActionDispatch::IntegrationTest
setup do
@user_without_onboarding = users(:one)
@user_without_onboarding.update!(onboarding_completed: false)
@user_with_onboarding = users(:two)
@user_with_onboarding.update!(onboarding_completed: true, first_name: "John", last_name: "Doe")
end
@@ -30,20 +30,20 @@ class OnboardingControllerTest < ActionDispatch::IntegrationTest
test "should complete onboarding with valid data" do
sign_in @user_without_onboarding
assert_not @user_without_onboarding.onboarding_completed?
post complete_onboarding_path, params: {
user: {
first_name: "Jane",
last_name: "Smith"
}
}
assert_redirected_to dashboard_path
follow_redirect!
assert_select ".notification", /Bienvenue sur Aperonight/
@user_without_onboarding.reload
assert @user_without_onboarding.onboarding_completed?
assert_equal "Jane", @user_without_onboarding.first_name
@@ -52,34 +52,34 @@ class OnboardingControllerTest < ActionDispatch::IntegrationTest
test "should not complete onboarding without required fields" do
sign_in @user_without_onboarding
post complete_onboarding_path, params: {
user: {
first_name: "",
last_name: "Smith"
}
}
assert_response :success
assert_select ".notification", /Veuillez remplir tous les champs requis/
@user_without_onboarding.reload
assert_not @user_without_onboarding.onboarding_completed?
end
test "should not complete onboarding without last name" do
sign_in @user_without_onboarding
post complete_onboarding_path, params: {
user: {
first_name: "Jane",
last_name: ""
}
}
assert_response :success
assert_select ".notification", /Veuillez remplir tous les champs requis/
@user_without_onboarding.reload
assert_not @user_without_onboarding.onboarding_completed?
end

View File

@@ -24,7 +24,7 @@ class TicketMailerTest < ActionMailer::TestCase
assert_equal [ "no-reply@aperonight.fr" ], email.from
assert_equal [ @user.email ], email.to
assert_equal "Confirmation d'achat - #{@event.name}", email.subject
# Check if we have any content
content = ""
if email.html_part
@@ -34,7 +34,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
# If still empty, try to get content from parts
if content.empty? && email.parts.any?
email.parts.each do |part|
@@ -44,7 +44,7 @@ class TicketMailerTest < ActionMailer::TestCase
end
end
end
# Instead of strict matching, just check that content exists
assert content.length > 0, "Email body should not be empty"
assert_match @event.name, content
@@ -64,7 +64,7 @@ class TicketMailerTest < ActionMailer::TestCase
assert_equal [ "no-reply@aperonight.fr" ], email.from
assert_equal [ @ticket.user.email ], email.to
assert_equal "Confirmation d'achat - #{@ticket.event.name}", email.subject
# Check if we have any content
content = ""
if email.html_part
@@ -74,7 +74,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
# If still empty, try to get content from parts
if content.empty? && email.parts.any?
email.parts.each do |part|
@@ -84,7 +84,7 @@ class TicketMailerTest < ActionMailer::TestCase
end
end
end
# Instead of strict matching, just check that content exists
assert content.length > 0, "Email body should not be empty"
assert_match @ticket.event.name, content
@@ -105,7 +105,7 @@ class TicketMailerTest < ActionMailer::TestCase
assert_equal [ "no-reply@aperonight.fr" ], email.from
assert_equal [ @user.email ], email.to
assert_equal "Rappel : #{@event.name} dans une semaine", email.subject
# Check content properly
content = ""
if email.html_part
@@ -115,7 +115,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
assert content.length > 0, "Email body should not be empty"
assert_match /une semaine/, content
assert_match @event.name, content
@@ -136,7 +136,7 @@ class TicketMailerTest < ActionMailer::TestCase
end
assert_equal "Rappel : #{@event.name} demain", email.subject
# Check content properly
content = ""
if email.html_part
@@ -146,7 +146,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
assert content.length > 0, "Email body should not be empty"
assert_match /demain/, content
end
@@ -161,7 +161,7 @@ class TicketMailerTest < ActionMailer::TestCase
end
assert_equal "C'est aujourd'hui : #{@event.name}", email.subject
# Check content properly
content = ""
if email.html_part
@@ -171,7 +171,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
assert content.length > 0, "Email body should not be empty"
assert_match /aujourd'hui/, content
end
@@ -186,7 +186,7 @@ class TicketMailerTest < ActionMailer::TestCase
end
assert_equal "Rappel : #{@event.name} dans 3 jours", email.subject
# Check content properly
content = ""
if email.html_part
@@ -196,7 +196,7 @@ class TicketMailerTest < ActionMailer::TestCase
else
content = email.body.to_s
end
assert content.length > 0, "Email body should not be empty"
assert_match /3 jours/, content
end

View File

@@ -74,21 +74,21 @@ class UserTest < ActiveSupport::TestCase
test "should complete onboarding" do
user = users(:one)
user.update!(onboarding_completed: false)
assert user.needs_onboarding?, "User should need onboarding initially"
user.complete_onboarding!
assert_not user.needs_onboarding?, "User should not need onboarding after completion"
assert user.onboarding_completed?, "User should have completed onboarding"
end
test "needs_onboarding? should return correct value" do
user = users(:one)
user.update!(onboarding_completed: false)
assert user.needs_onboarding?, "User with false onboarding_completed should need onboarding"
user.update!(onboarding_completed: true)
assert_not user.needs_onboarding?, "User with true onboarding_completed should not need onboarding"
end

View File

@@ -17,7 +17,7 @@ module ActiveSupport
fixtures :all
# Add more helper methods to be used by all tests here...
# Helper to create users with completed onboarding by default for tests
def create_test_user(attributes = {})
User.create!({