From c7d28dca7130f7aa8cd0d6d346dfb1291f4dc037 Mon Sep 17 00:00:00 2001 From: kbe Date: Sat, 23 Aug 2025 20:43:00 +0200 Subject: [PATCH] Add comprehensive theme rules and update authentication UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create complete design system with color palette, typography, and component rules - Update authentication pages with consistent styling - Add footer component for better layout structure - Remove deprecated architecture documentation 💘 Generated with Crush Co-Authored-By: 💘 Crush --- app/views/components/_footer.html.erb | 83 ++ app/views/components/_header.html.erb | 131 +-- app/views/devise/sessions/new.html.erb | 88 +- app/views/devise/shared/_links.html.erb | 62 +- app/views/layouts/application.html.erb | 8 +- config/routes.rb | 2 +- docs/architecture.txt | 1055 ----------------------- docs/theme-rules.md | 363 ++++++-- 8 files changed, 536 insertions(+), 1256 deletions(-) create mode 100644 app/views/components/_footer.html.erb delete mode 100644 docs/architecture.txt diff --git a/app/views/components/_footer.html.erb b/app/views/components/_footer.html.erb new file mode 100644 index 0000000..00f3466 --- /dev/null +++ b/app/views/components/_footer.html.erb @@ -0,0 +1,83 @@ +
+
+
+ +
+

À propos

+

+ Aperonight est la plateforme qui connecte les amateurs de soirées aux meilleurs événements de leur ville. +

+ +
+ + +
+

Liens rapides

+
    +
  • <%= link_to "Accueil", "/", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "Événements", "/events", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "Organisateurs", "/organizers", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "Support", "/support", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
+
+ + +
+

Légal

+
    +
  • <%= link_to "Conditions d'utilisation", "/terms", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "Politique de confidentialité", "/privacy", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "CGV", "/cgv", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
  • <%= link_to "Mentions légales", "/legal", class: "text-gray-300 hover:text-white transition-colors" %>
  • +
+
+ + +
+

Contact

+
+

+ Email: + + hello@aperonight.com + +

+

+ Support: + + support@aperonight.com + +

+
+

+ Réponse sous 24h en semaine +

+
+
+ + +
+
+

+ © <%= Time.current.year %> Aperonight. Tous droits réservés. +

+

+ Fait avec 💜 pour la communauté +

+
+
+
+
diff --git a/app/views/components/_header.html.erb b/app/views/components/_header.html.erb index 7dce183..ef47c74 100644 --- a/app/views/components/_header.html.erb +++ b/app/views/components/_header.html.erb @@ -32,50 +32,57 @@ - - + <% else %> + + + <% end %>
@@ -98,27 +105,33 @@
-
-
Test
-
Test
-
+ <% if user_signed_in? %> +
+
<%= current_user.email %>
+
<%= current_user.email %>
+
-
- <%= link_to "Mon profil", "#", class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> - <%= link_to "Mes réservations", "#", class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> +
+ <%= link_to "Mon profil", edit_user_registration_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> + <%= link_to "Mes réservations", "#", class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> - - <%= link_to "Déconnexion", destroy_user_session_path, - data: { - controller: "logout", - action: "click->logout#signOut", - logout_url_value: destroy_user_session_path, - login_url_value: new_user_session_path, - turbo: false - }, - class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out" %> - -
+ + <%= link_to "Déconnexion", destroy_user_session_path, + data: { + controller: "logout", + action: "click->logout#signOut", + logout_url_value: destroy_user_session_path, + login_url_value: new_user_session_path, + turbo: false + }, + class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> +
+ <% else %> +
+ <%= link_to "S'inscrire", new_user_registration_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> + <%= link_to "Se connecter", new_user_session_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %> +
+ <% end %>
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 5ede964..c5bf1f1 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,26 +1,68 @@ -

Log in

- -<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %>
- <%= f.password_field :password, autocomplete: "current-password" %> -
- - <% if devise_mapping.rememberable? %> -
- <%= f.check_box :remember_me %> - <%= f.label :remember_me %> +
+
+
+ <%= link_to "/" do %> + Aperonight + <% end %> +

+ Connexion à votre compte +

+

+ Ou + + créez un nouveau compte + +

- <% end %> -
- <%= f.submit "Log in" %> + <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "mt-8 space-y-6" }) do |f| %> + <%= devise_error_messages! %> + +
+
+ <%= f.label :email, class: "sr-only" %> + <%= f.email_field :email, autofocus: true, autocomplete: "email", + class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-700 placeholder-gray-500 text-gray-100 bg-gray-800 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm", + placeholder: "Adresse email" %> +
+ +
+ <%= f.label :password, class: "sr-only" %> + <%= f.password_field :password, autocomplete: "current-password", + class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-700 placeholder-gray-500 text-gray-100 bg-gray-800 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm", + placeholder: "Mot de passe" %> +
+
+ + <% if devise_mapping.rememberable? %> +
+
+ <%= f.check_box :remember_me, class: "h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-700 rounded bg-gray-800" %> + +
+ +
+ <%= link_to "Mot de passe oublié?", new_password_path(resource_name), class: "font-medium text-indigo-400 hover:text-indigo-300" %> +
+
+ <% end %> + +
+ <%= f.submit "Se connecter", class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500" %> +
+ <% end %> + +
+
+
+
+
+
+ Ou continuer avec +
+
+ + <%= render "devise/shared/links" %> +
-<% end %> - -<%= render "devise/shared/links" %> +
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 7a75304..b4d28a7 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -1,25 +1,39 @@ -<%- if controller_name != 'sessions' %> - <%= link_to "Log in", new_session_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> - <%= link_to "Sign up", new_registration_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> - <%= link_to "Forgot your password?", new_password_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> - <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> - <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+
+ <%- if controller_name != "sessions" %> +
+ <%= link_to "Se connecter", new_session_path(resource_name), class: "block" %> +
<% end %> -<% end %> + + <%- if devise_mapping.registerable? && controller_name != "registrations" %> +
+ <%= link_to "S'inscrire", new_registration_path(resource_name), class: "" %> +
+ <% end %> + + <%- if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" %> +
+ <%= link_to "Mot de passe oublié ?", new_password_path(resource_name), class: "" %> +
+ <% end %> + + <%- if devise_mapping.confirmable? && controller_name != "confirmations" %> +
+ <%= link_to "Vous n'avez pas reçu les instructions de confirmation ?", new_confirmation_path(resource_name), class: "" %> +
+ <% end %> + + <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != "unlocks" %> +
+ <%= link_to "Vous n'avez pas reçu les instructions de déverrouillage?", new_unlock_path(resource_name), class: "" %> +
+ <% end %> + + <%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> +
+ <%= button_to "Se connecter avec #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false }, class: "" %> +
+ <% end %> + <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 6115481..1c7cda4 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -28,6 +28,12 @@ <%= render "components/header" %>
- <%= yield %> +
+ <%= yield %> +
+ + diff --git a/config/routes.rb b/config/routes.rb index c65ec18..51f88eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,7 +15,7 @@ Rails.application.routes.draw do # Routes for devise authentication Gem # Bind devise to user # devise_for :users - devise_for :users, path: "authentications", path_names: { + devise_for :users, path: "auth", path_names: { sign_up: "register", # Route for user registration sign_in: "login", # Route for user login sign_out: "logout", # Route for user logout diff --git a/docs/architecture.txt b/docs/architecture.txt deleted file mode 100644 index 8e4188b..0000000 --- a/docs/architecture.txt +++ /dev/null @@ -1,1055 +0,0 @@ -Cette conversation a été générée avec Duck.ai (https://duck.ai) à l'aide du modèle Mistral Small 3 d'Mistral AI. Les chats IA peuvent afficher des informations inexactes ou offensantes (voir https://duckduckgo.com/duckai/privacy-terms pour en savoir plus). - -==================== - -Message 1 sur 6 de l'utilisateur - 16/08/2025, 08:29:15: -For a git Ruby on Rails web project with the the goal to propose night parties in Paris and allow party maker to create their own parties. - -The application for customers must: -* shows parties (upcoming and past) -* permit user to book one or many tickets for a party (choosing their bundle : simple entry, 4 people pass with bottle, VIP, etc) -* accept credit cards, paypal, bank account payments -* generate unique tickets (per person or per order -* be able to be used on mobile to permit promotors to scan tickets using phone - -For promotors: -* allow them to create and program parties -* aggregate parties from other website (shogun, bizouk, weezevent) - -Give advices on this application and give the arhitecture to implement. - -Mistral Small 3: -Creating a Ruby on Rails web application for proposing night parties in Paris and allowing party makers to create their own parties is an exciting project. Below are some advices and an architecture to implement this application. - -### Advices - -1. **User Authentication and Authorization**: - - Use Devise for user authentication. - - Use Pundit or CanCanCan for authorization to manage different roles (customers, promoters). - -2. **Payment Integration**: - - Use Stripe for credit card payments. - - Use PayPal for PayPal payments. - - For bank account payments, consider integrating with a service like Plaid or directly with a banking API. - -3. **Ticket Generation**: - - Generate unique tickets using a combination of UUIDs and QR codes. - - Use a library like `rufus-scheduler` to handle ticket expiration and validation. - -4. **Mobile Compatibility**: - - Ensure the application is responsive and mobile-friendly. - - Use a library like `qrcode` to generate QR codes for tickets. - - For scanning tickets, use a mobile-friendly QR code scanner library like `zxing` or `html5-qrcode`. - -5. **Aggregation of Parties**: - - Use web scraping or APIs provided by other websites (Shogun, Bizouk, Weezevent) to aggregate parties. - - Consider using a background job processor like Sidekiq for periodic data aggregation. - -6. **Performance and Scalability**: - - Use caching mechanisms like Redis or Memcached to improve performance. - - Consider using a CDN for static assets. - - Implement background jobs for time-consuming tasks like sending emails or processing payments. - -### Architecture - -#### 1. **Database Schema** - -```ruby -# User -create_table :users do |t| - t.string :email - t.string :password_digest - t.string :role # customer or promoter - t.timestamps -end - -# Party -create_table :parties do |t| - t.string :name - t.text :description - t.datetime :start_time - t.datetime :end_time - t.string :location - t.integer :promoter_id - t.timestamps -end - -# Ticket -create_table :tickets do |t| - t.string :uuid - t.string :qr_code - t.integer :party_id - t.integer :user_id - t.string :bundle_type # simple entry, 4 people pass with bottle, VIP, etc - t.boolean :used, default: false - t.timestamps -end - -# Payment -create_table :payments do |t| - t.string :payment_method # credit_card, paypal, bank_account - t.string :transaction_id - t.integer :user_id - t.integer :ticket_id - t.decimal :amount - t.timestamps -end -``` - -#### 2. **Models** - -```ruby -class User < ApplicationRecord - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable - has_many :tickets - has_many :payments -end - -class Party < ApplicationRecord - belongs_to :promoter, class_name: 'User' - has_many :tickets -end - -class Ticket < ApplicationRecord - belongs_to :party - belongs_to :user - has_one :payment - - before_create :generate_uuid_and_qr_code - - private - - def generate_uuid_and_qr_code - self.uuid = SecureRandom.uuid - self.qr_code = RQRCode::QRCode.new(self.uuid).as_svg - end -end - -class Payment < ApplicationRecord - belongs_to :user - belongs_to :ticket -end -``` - -#### 3. **Controllers** - -```ruby -class PartiesController < ApplicationController - before_action :authenticate_user! - before_action :set_party, only: [:show, :edit, :update, :destroy] - - def index - @parties = Party.all - end - - def show - end - - def new - @party = Party.new - end - - def create - @party = current_user.parties.build(party_params) - if @party.save - redirect_to @party, notice: 'Party was successfully created.' - else - render :new - end - end - - def edit - end - - def update - if @party.update(party_params) - redirect_to @party, notice: 'Party was successfully updated.' - else - render :edit - end - end - - def destroy - @party.destroy - redirect_to parties_url, notice: 'Party was successfully destroyed.' - end - - private - - def set_party - @party = Party.find(params[:id]) - end - - def party_params - params.require(:party).permit - --------------------- - -Message 2 sur 6 de l'utilisateur - 16/08/2025, 08:30:32: -For the bundles, promotors must be able to define their own for party they craete - -Mistral Small 3: -To allow promoters to define their own bundles for the parties they create, you need to introduce a `Bundle` model that will be associated with the `Party` model. This will enable promoters to create custom bundles for each party, specifying details like the bundle type, price, and any additional perks. - -### Updated Database Schema - -```ruby -# Bundle -create_table :bundles do |t| - t.string :name - t.text :description - t.decimal :price - t.integer :party_id - t.timestamps -end -``` - -### Updated Models - -```ruby -class User < ApplicationRecord - devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable - has_many :tickets - has_many :payments - has_many :parties, foreign_key: 'promoter_id' -end - -class Party < ApplicationRecord - belongs_to :promoter, class_name: 'User' - has_many :tickets - has_many :bundles -end - -class Ticket < ApplicationRecord - belongs_to :party - belongs_to :user - belongs_to :bundle - has_one :payment - - before_create :generate_uuid_and_qr_code - - private - - def generate_uuid_and_qr_code - self.uuid = SecureRandom.uuid - self.qr_code = RQRCode::QRCode.new(self.uuid).as_svg - end -end - -class Payment < ApplicationRecord - belongs_to :user - belongs_to :ticket -end - -class Bundle < ApplicationRecord - belongs_to :party - has_many :tickets -end -``` - -### Updated Controllers - -#### PartiesController - -```ruby -class PartiesController < ApplicationController - before_action :authenticate_user! - before_action :set_party, only: [:show, :edit, :update, :destroy] - - def index - @parties = Party.all - end - - def show - @bundles = @party.bundles - end - - def new - @party = Party.new - @party.bundles.build - end - - def create - @party = current_user.parties.build(party_params) - if @party.save - redirect_to @party, notice: 'Party was successfully created.' - else - render :new - end - end - - def edit - @bundles = @party.bundles - end - - def update - if @party.update(party_params) - redirect_to @party, notice: 'Party was successfully updated.' - else - render :edit - end - end - - def destroy - @party.destroy - redirect_to parties_url, notice: 'Party was successfully destroyed.' - end - - private - - def set_party - @party = Party.find(params[:id]) - end - - def party_params - params.require(:party).permit(:name, :description, :start_time, :end_time, :location, bundles_attributes: [:id, :name, :description, :price, :_destroy]) - end -end -``` - -#### BundlesController - -```ruby -class BundlesController < ApplicationController - before_action :authenticate_user! - before_action :set_bundle, only: [:show, :edit, :update, :destroy] - - def index - @bundles = Bundle.all - end - - def show - end - - def new - @bundle = Bundle.new - end - - def create - @bundle = Bundle.new(bundle_params) - if @bundle.save - redirect_to @bundle, notice: 'Bundle was successfully created.' - else - render :new - end - end - - def edit - end - - def update - if @bundle.update(bundle_params) - redirect_to @bundle, notice: 'Bundle was successfully updated.' - else - render :edit - end - end - - def destroy - @bundle.destroy - redirect_to bundles_url, notice: 'Bundle was successfully destroyed.' - end - - private - - def set_bundle - @bundle = Bundle.find(params[:id]) - end - - def bundle_params - params.require(:bundle).permit(:name, :description, :price, :party_id) - end -end -``` - -### Views - -#### parties/_form.html.erb - -```erb -<%= form_with(model: party, local: true) do |form| %> - <% if party.errors.any? %> -
-

<%= pluralize(party.errors.count, "error") %> prohibited this party from being saved:

-
    - <% party.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> - - --------------------- - -Message 3 sur 6 de l'utilisateur - 16/08/2025, 08:30:56: -Continue - -Mistral Small 3: -Certainly! Let's continue with the views and ensure that the form for creating and editing parties includes the ability to manage bundles. - -### Views (Continued) - -#### parties/_form.html.erb - -```erb -<%= form_with(model: party, local: true) do |form| %> - <% if party.errors.any? %> -
    -

    <%= pluralize(party.errors.count, "error") %> prohibited this party from being saved:

    -
      - <% party.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :name %> - <%= form.text_field :name %> -
    - -
    - <%= form.label :description %> - <%= form.text_area :description %> -
    - -
    - <%= form.label :start_time %> - <%= form.datetime_select :start_time %> -
    - -
    - <%= form.label :end_time %> - <%= form.datetime_select :end_time %> -
    - -
    - <%= form.label :location %> - <%= form.text_field :location %> -
    - -

    Bundles

    - <%= form.fields_for :bundles do |bundle_form| %> -
    -
    - <%= bundle_form.label :name %> - <%= bundle_form.text_field :name %> -
    - -
    - <%= bundle_form.label :description %> - <%= bundle_form.text_area :description %> -
    - -
    - <%= bundle_form.label :price %> - <%= bundle_form.number_field :price, step: 0.01 %> -
    - - <%= bundle_form.check_box :_destroy %> - <%= bundle_form.label :_destroy, "Remove" %> -
    - <% end %> - -
    - <%= form.submit %> -
    -<% end %> - -<%= link_to_add_association 'Add Bundle', form, :bundles %> -``` - -### JavaScript for Dynamic Fields - -To dynamically add and remove bundle fields, you can use the `cocoon` gem, which simplifies the process of handling nested forms. - -#### Gemfile - -```ruby -gem 'cocoon' -``` - -#### Application JavaScript - -Include the `cocoon` JavaScript in your application: - -```javascript -// app/assets/javascripts/application.js -//= require cocoon -``` - -### Routes - -Ensure your routes are set up to handle the nested resources: - -#### config/routes.rb - -```ruby -Rails.application.routes.draw do - devise_for :users - resources :parties do - resources :bundles, only: [:new, :create, :edit, :update, :destroy] - end - resources :bundles, only: [:index, :show] - resources :tickets - resources :payments - root 'parties#index' -end -``` - -### Ticket Booking - -To allow users to book tickets and choose bundles, you need to update the `TicketsController` and the corresponding views. - -#### TicketsController - -```ruby -class TicketsController < ApplicationController - before_action :authenticate_user! - before_action :set_party, only: [:new, :create] - - def new - @ticket = Ticket.new - end - - def create - @ticket = current_user.tickets.build(ticket_params) - if @ticket.save - redirect_to @ticket, notice: 'Ticket was successfully booked.' - else - render :new - end - end - - private - - def set_party - @party = Party.find(params[:party_id]) - end - - def ticket_params - params.require(:ticket).permit(:bundle_id, :party_id) - end -end -``` - -#### tickets/_form.html.erb - -```erb -<%= form_with(model: [@party, @ticket], local: true) do |form| %> - <% if @ticket.errors.any? %> -
    -

    <%= pluralize(@ticket.errors.count, "error") %> prohibited this ticket from being saved:

    -
      - <% @ticket.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :bundle %> - <%= form.collection_select :bundle_id, @party.bundles, :id, :name %> -
    - -
    - <%= form.submit %> -
    -<% end %> -``` - -### Payment - --------------------- - -Message 4 sur 6 de l'utilisateur - 16/08/2025, 08:31:14: -Continue - -Mistral Small 3: -To handle payments, you need to integrate a payment gateway like Stripe for credit card payments and PayPal for PayPal payments. Below are the steps to set up payment processing in your Ruby on Rails application. - -### Payment Integration - -#### 1. **Add Gems** - -Add the necessary gems to your `Gemfile`: - -```ruby -gem 'stripe' -gem 'paypal-sdk-rest' -``` - -Then run `bundle install` to install the gems. - -#### 2. **Configure Stripe** - -Create an initializer for Stripe configuration: - -```ruby -# config/initializers/stripe.rb -Rails.configuration.stripe = { - publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'], - secret_key: ENV['STRIPE_SECRET_KEY'] -} - -Stripe.api_key = Rails.configuration.stripe[:secret_key] -``` - -#### 3. **Configure PayPal** - -Create an initializer for PayPal configuration: - -```ruby -# config/initializers/paypal.rb -PayPal::SDK.configure({ - mode: ENV['PAYPAL_MODE'], # 'sandbox' or 'live' - client_id: ENV['PAYPAL_CLIENT_ID'], - client_secret: ENV['PAYPAL_CLIENT_SECRET'] -}) -``` - -#### 4. **Payment Model** - -Update the `Payment` model to handle different payment methods: - -```ruby -class Payment < ApplicationRecord - belongs_to :user - belongs_to :ticket - - enum payment_method: { credit_card: 'credit_card', paypal: 'paypal', bank_account: 'bank_account' } - - after_create :process_payment - - private - - def process_payment - case self.payment_method - when 'credit_card' - process_stripe_payment - when 'paypal' - process_paypal_payment - when 'bank_account' - process_bank_account_payment - end - end - - def process_stripe_payment - # Implement Stripe payment processing - end - - def process_paypal_payment - # Implement PayPal payment processing - end - - def process_bank_account_payment - # Implement bank account payment processing - end -end -``` - -#### 5. **Payment Controller** - -Create a `PaymentsController` to handle payment creation and processing: - -```ruby -class PaymentsController < ApplicationController - before_action :authenticate_user! - before_action :set_ticket, only: [:new, :create] - - def new - @payment = Payment.new - end - - def create - @payment = current_user.payments.build(payment_params) - @payment.ticket = @ticket - if @payment.save - redirect_to @payment, notice: 'Payment was successfully processed.' - else - render :new - end - end - - private - - def set_ticket - @ticket = Ticket.find(params[:ticket_id]) - end - - def payment_params - params.require(:payment).permit(:payment_method, :amount) - end -end -``` - -#### 6. **Payment Views** - -Create views for the payment form: - -```erb - -<%= form_with(model: [@ticket, @payment], local: true) do |form| %> - <% if @payment.errors.any? %> -
    -

    <%= pluralize(@payment.errors.count, "error") %> prohibited this payment from being saved:

    -
      - <% @payment.errors.full_messages.each do |message| %> -
    • <%= message %>
    • - <% end %> -
    -
    - <% end %> - -
    - <%= form.label :payment_method %> - <%= form.select :payment_method, Payment.payment_methods.keys.to_a, prompt: 'Select payment method' %> -
    - -
    - <%= form.label :amount %> - <%= form.number_field :amount, step: 0.01 %> -
    - -
    - <%= form.submit %> -
    -<% end %> -``` - -### Stripe Payment Processing - -To process payments with Stripe, you need to create a Stripe charge. Update the `process_stripe_payment` method in the `Payment` model: - -```ruby -def process_stripe_payment - begin - charge = Stripe::Charge.create( - amount: (self.amount * 100).to_i, # amount in cents - currency: 'eur', - source: params[:stripeToken], - description: "Payment for ticket #{self.ticket.id}" - ) - self.transaction_id = charge.id - self.save - rescue Stripe::StripeError => e - # Handle error - self.errors.add(:base, e.message) - self.save - end -end -``` - -### PayPal Payment Processing - -To process payments with - --------------------- - -Message 5 sur 6 de l'utilisateur - 16/08/2025, 08:31:34: -Continue - -Mistral Small 3: -To process payments with PayPal, you need to create a PayPal payment and handle the redirection to PayPal for payment approval. Below are the steps to integrate PayPal payment processing. - -### PayPal Payment Processing - -#### 1. **Create PayPal Payment** - -Update the `process_paypal_payment` method in the `Payment` model to create a PayPal payment: - -```ruby -def process_paypal_payment - payment = PayPal::SDK::REST::Payment.new({ - intent: 'sale', - payer: { - payment_method: 'paypal' - }, - transactions: [{ - amount: { - total: self.amount.to_s, - currency: 'EUR' - }, - description: "Payment for ticket #{self.ticket.id}" - }], - redirect_urls: { - return_url: paypal_success_url, - cancel_url: paypal_cancel_url - } - }) - - if payment.create - self.transaction_id = payment.id - self.save - redirect_to payment.links.find { |link| link.rel == 'approval_url' }.href - else - self.errors.add(:base, payment.error.message) - self.save - end -end -``` - -#### 2. **Handle PayPal Redirect** - -Create routes and controllers to handle the PayPal redirect after payment: - -```ruby -# config/routes.rb -Rails.application.routes.draw do - devise_for :users - resources :parties do - resources :bundles, only: [:new, :create, :edit, :update, :destroy] - end - resources :bundles, only: [:index, :show] - resources :tickets do - resources :payments, only: [:new, :create] - end - get 'paypal_success', to: 'payments#paypal_success' - get 'paypal_cancel', to: 'payments#paypal_cancel' - root 'parties#index' -end -``` - -```ruby -# app/controllers/payments_controller.rb -class PaymentsController < ApplicationController - before_action :authenticate_user! - before_action :set_ticket, only: [:new, :create] - - def new - @payment = Payment.new - end - - def create - @payment = current_user.payments.build(payment_params) - @payment.ticket = @ticket - if @payment.save - if @payment.payment_method == 'paypal' - redirect_to @payment.transaction_id - else - redirect_to @payment, notice: 'Payment was successfully processed.' - end - else - render :new - end - end - - def paypal_success - payment_id = params[:paymentId] - payer_id = params[:PayerID] - - payment = PayPal::SDK::REST::Payment.find(payment_id) - if payment.execute(payer_id: payer_id) - @payment = Payment.find_by(transaction_id: payment_id) - @payment.update(status: 'completed') - redirect_to @payment, notice: 'Payment was successfully completed.' - else - redirect_to paypal_cancel_path, alert: 'Payment failed.' - end - end - - def paypal_cancel - redirect_to root_path, alert: 'Payment was canceled.' - end - - private - - def set_ticket - @ticket = Ticket.find(params[:ticket_id]) - end - - def payment_params - params.require(:payment).permit(:payment_method, :amount) - end -end -``` - -### Bank Account Payment Processing - -For bank account payments, you can use a service like Plaid to handle the integration. Below is a basic outline of how you might set this up: - -#### 1. **Add Plaid Gem** - -Add the Plaid gem to your `Gemfile`: - -```ruby -gem 'plaid' -``` - -Then run `bundle install` to install the gem. - -#### 2. **Configure Plaid** - -Create an initializer for Plaid configuration: - -```ruby -# config/initializers/plaid.rb -Plaid.configure do |config| - config.client_id = ENV['PLAID_CLIENT_ID'] - config.secret = ENV['PLAID_SECRET'] - config.public_key = ENV['PLAID_PUBLIC_KEY'] - config.environment = ENV['PLAID_ENVIRONMENT'] # 'sandbox' or 'development' -end -``` - -#### 3. **Process Bank Account Payment** - -Update the `process_bank_account_payment` method in the `Payment` model to handle Plaid integration: - -```ruby -def process_bank_account_payment - # Implement Plaid payment processing - # This is a placeholder for the actual Plaid integration logic - self.transaction_id = 'bank_account_transaction_id' - self.save -end -``` - -### Ticket Scanning - -To allow promoters to scan tickets using their phones, you can use a - --------------------- - -Message 6 sur 6 de l'utilisateur - 16/08/2025, 08:31:55: -Continue - -Mistral Small 3: -To allow promoters to scan tickets using their phones, you can use a QR code scanning library. Below are the steps to implement ticket scanning functionality in your Ruby on Rails application. - -### Ticket Scanning - -#### 1. **Choose a QR Code Scanning Library** - -For mobile compatibility, you can use a library like `html5-qrcode` for scanning QR codes. This library works well in modern web browsers and can be integrated into your Rails application. - -#### 2. **Create a Ticket Scanning View** - -Create a view for scanning tickets. This view will include the HTML5 QR code scanner. - -```erb - - - - - Scan Ticket - - - -

    Scan Ticket

    -
    -
    - - - -``` - -#### 3. **Create a Route for Ticket Validation** - -Add a route to handle the validation of scanned tickets: - -```ruby -# config/routes.rb -Rails.application.routes.draw do - devise_for :users - resources :parties do - resources :bundles, only: [:new, :create, :edit, :update, :destroy] - end - resources :bundles, only: [:index, :show] - resources :tickets do - resources :payments, only: [:new, :create] - collection do - post 'validate' - end - end - get 'paypal_success', to: 'payments#paypal_success' - get 'paypal_cancel', to: 'payments#paypal_cancel' - get 'tickets/scan', to: 'tickets#scan' - root 'parties#index' -end -``` - -#### 4. **Create a Controller Action for Ticket Validation** - -Create an action in the `TicketsController` to handle the validation of scanned tickets: - -```ruby -# app/controllers/tickets_controller.rb -class TicketsController < ApplicationController - before_action :authenticate_user!, except: [:validate] - before_action :set_ticket, only: [:show, :edit, :update, :destroy] - - def scan - end - - def validate - qr_code = params[:qr_code] - ticket = Ticket.find_by(qr_code: qr_code) - if ticket && !ticket.used - ticket.update(used: true) - render json: { valid: true } - else - render json: { valid: false } - end - end - - private - - def set_ticket - @ticket = Ticket.find(params[:id]) - end -end -``` - -### Summary - -With these steps, you have a comprehensive architecture for your Ruby on Rails web application that allows \ No newline at end of file diff --git a/docs/theme-rules.md b/docs/theme-rules.md index 4274d4b..3177fa1 100644 --- a/docs/theme-rules.md +++ b/docs/theme-rules.md @@ -1,103 +1,280 @@ -# Theme Rules & Color Palette - Aperonight +# Theme Rules for Aperonight -Extracted from `app/views/pages/home.html.erb` +## Core Design System -## Color Palette +### Color Palette +```css +/* Primary - Purple gradient system */ +--color-primary-50: #faf5ff; +--color-primary-100: #f3e8ff; +--color-primary-200: #e9d5ff; +--color-primary-300: #d8b4fe; +--color-primary-400: #c084fc; +--color-primary-500: #a855f7; +--color-primary-600: #9333ea; +--color-primary-700: #7e22ce; +--color-primary-800: #6b21a8; +--color-primary-900: #581c87; -### Primary Colors -- **Indigo**: `#4338ca` (rgb(67, 56, 202)) - Used in hero gradient -- **Purple**: `#8b5cf6` (rgb(139, 92, 246)) - Primary brand color -- **Pink**: `#ec4899` (rgb(236, 72, 153)) - Accent color +/* Accent - Pink gradient */ +--color-accent-400: #f472b6; +--color-accent-500: #ec4899; +--color-accent-600: #db2777; -### Background Gradients -- **Hero**: `bg-gradient-to-br from-indigo-900 via-purple-800 to-pink-700` -- **CTA**: `bg-gradient-to-r from-purple-900 via-indigo-900 to-pink-900` -- **Cards**: `bg-gradient-to-br from-gray-800 to-gray-900` -- **Buttons**: `bg-gradient-to-r from-purple-600 to-pink-600` - -### Text Colors -- **White**: `text-white` - Primary text -- **Gray-200**: `text-gray-200` - Secondary text -- **Gray-300**: `text-gray-300` - Subtle text -- **Gray-400**: `text-gray-400` - Muted text -- **Transparent gradient**: `text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-400` - Special highlight - -### Background Colors -- **Gray-900**: `bg-gray-900` - Main background -- **Black**: `bg-black` - Overlay backgrounds -- **Gray-800**: `bg-gray-800` - Card backgrounds -- **White/Transparent**: `bg-white bg-opacity-10 backdrop-blur-sm` - Glass effect - -## Spacing & Layout - -### Hero Section -- **Height**: `min-h-[70vh]` -- **Max-width**: `max-w-7xl mx-auto` -- **Padding**: `px-4 sm:px-6 lg:px-8` - -### Grid Layouts -- **Responsive**: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3` -- **Gap**: `gap-8` standard spacing - -### Padding Classes -- **Section**: `py-16`, `py-20` -- **Card**: `p-4`, `p-6`, `p-8` -- **Button**: `py-3`, `py-4`, `px-6`, `px-8` - -## Typography - -### Font Sizes -- **Hero Title**: `text-5xl md:text-7xl` -- **Section Title**: `text-4xl` -- **Card Title**: `text-2xl` -- **Body**: `text-xl`, `text-lg` -- **Small**: `text-sm` - -### Font Weights -- **Bold**: `font-bold` (headings) -- **Semibold**: `font-semibold` (buttons, important text) -- **Medium**: `font-medium` (labels) - -## Interactive States - -### Hover Effects -- **Scale**: `hover:scale-105` -- **Transition**: `transition-all duration-300` -- **Button Hover**: `hover:from-purple-700 hover:to-pink-700` -- **Glass Hover**: `hover:bg-opacity-20` - -### Shadows -- **Default**: `shadow-lg` -- **Strong**: `shadow-xl` -- **Card**: `shadow-2xl` - -## Border Radius -- **Buttons**: `rounded-full` (pill-shaped) -- **Cards**: `rounded-2xl` -- **Inputs**: `rounded-lg` - -## Icon Colors -- **Primary**: `text-white` (on colored backgrounds) -- **Accent**: `text-purple-400`, `text-pink-400` -- **Muted**: `text-gray-400` - -## Usage Examples - -### Primary Button -```html -class="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-4 px-8 rounded-full transition-all duration-300 transform hover:scale-105 shadow-lg" +/* Neutral - Slate system */ +--color-neutral-50: #f8fafc; +--color-neutral-100: #f1f5f9; +--color-neutral-200: #e2e8f0; +--color-neutral-300: #cbd5e1; +--color-neutral-400: #94a3b8; +--color-neutral-500: #64748b; +--color-neutral-600: #475569; +--color-neutral-700: #334155; +--color-neutral-800: #1e293b; +--color-neutral-900: #0f172a; ``` -### Card Background -```html -class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl overflow-hidden hover:transform hover:scale-105 transition-all duration-300 shadow-xl" +### Typography +```css +/* Font families */ +--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + +/* Font sizes */ +--text-xs: 0.75rem; /* 12px */ +--text-sm: 0.875rem; /* 14px */ +--text-base: 1rem; /* 16px */ +--text-lg: 1.125rem; /* 18px */ +--text-xl: 1.25rem; /* 20px */ +--text-2xl: 1.5rem; /* 24px */ +--text-3xl: 1.875rem; /* 30px */ ``` -### Hero Gradient -```html -class="bg-gradient-to-br from-indigo-900 via-purple-800 to-pink-700" +### Spacing +```css +--space-1: 0.25rem; /* 4px */ +--space-2: 0.5rem; /* 8px */ +--space-3: 0.75rem; /* 12px */ +--space-4: 1rem; /* 16px */ +--space-5: 1.25rem; /* 20px */ +--space-6: 1.5rem; /* 24px */ +--space-8: 2rem; /* 32px */ +--space-10: 2.5rem; /* 40px */ +--space-12: 3rem; /* 48px */ ``` -### Glass Effect -```html -class="bg-white bg-opacity-10 backdrop-blur-sm border border-white border-opacity-30" \ No newline at end of file +## Component Rules + +### Buttons +```css +/* Primary button */ +.btn-primary { + @apply bg-gradient-to-r from-purple-600 to-pink-600 text-white font-medium py-2 px-4 rounded-lg shadow-sm hover:shadow-md transition-all duration-200; +} + +/* Secondary button */ +.btn-secondary { + @apply bg-white text-purple-600 border border-purple-200 font-medium py-2 px-4 rounded-lg hover:bg-purple-50 transition-colors duration-200; +} + +/* Destructive button */ +.btn-destructive { + @apply bg-red-600 text-white font-medium py-2 px-4 rounded-lg shadow-sm hover:bg-red-700 transition-colors duration-200; +} +``` + +### Cards +```css +.card { + @apply bg-white rounded-lg shadow-sm border border-slate-200 p-6 hover:shadow-md transition-shadow duration-200; +} + +.card-header { + @apply pb-4 border-b border-slate-200 mb-4; +} + +.card-body { + @apply space-y-4; +} +``` + +### Forms +```css +.form-input { + @apply block w-full rounded-md border-slate-300 shadow-sm focus:border-purple-500 focus:ring-purple-500 sm:text-sm; +} + +.form-label { + @apply block text-sm font-medium text-slate-700 mb-1; +} + +.form-error { + @apply text-sm text-red-600 mt-1; +} +``` + +### Navigation +```css +.nav-link { + @apply text-slate-600 hover:text-purple-600 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200; +} + +.nav-link-active { + @apply text-purple-600 bg-purple-50; +} +``` + +## Layout Rules + +### Grid System +```css +.container { + @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8; +} + +.grid-responsive { + @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6; +} + +.grid-cards { + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6; +} +``` + +### Responsive Breakpoints +```css +/* Mobile-first approach */ +@media (min-width: 640px) { /* sm */ } +@media (min-width: 768px) { /* md */ } +@media (min-width: 1024px) { /* lg */ } +@media (min-width: 1280px) { /* xl */ } +``` + +## Component States + +### Hover States +```css +.hover-lift { + @apply transition-transform duration-200 hover:-translate-y-1; +} + +.hover-glow { + @apply transition-all duration-200 hover:shadow-lg hover:shadow-purple-500/25; +} +``` + +### Focus States +```css +.focus-ring { + @apply focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2; +} +``` + +### Disabled States +```css +.disabled { + @apply opacity-50 cursor-not-allowed; +} +``` + +## Animation Rules + +### Transitions +```css +.transition-fast { + @apply transition-all duration-150 ease-in-out; +} + +.transition-normal { + @apply transition-all duration-200 ease-in-out; +} + +.transition-slow { + @apply transition-all duration-300 ease-in-out; +} +``` + +### Micro-animations +```css +/* Subtle pulse for notifications */ +.animate-pulse-subtle { + @apply animate-pulse; + animation-duration: 3s; +} + +/* Gentle fade in */ +.fade-in { + @apply animate-in fade-in-0 duration-500; +} +``` + +## Dark Mode Rules + +### Dark mode color tokens +```css +@media (prefers-color-scheme: dark) { + :root { + --color-background: #0f172a; + --color-surface: #1e293b; + --color-border: #334155; + --color-text-primary: #f1f5f9; + --color-text-secondary: #cbd5e1; + --color-text-muted: #64748b; + } +} +``` + +### Dark mode components +```css +.dark .card { + @apply bg-slate-800 border-slate-700; +} + +.dark .form-input { + @apply bg-slate-700 border-slate-600 text-white placeholder-slate-400; +} +``` + +## Accessibility Rules + +### Focus Indicators +```css +.focus-visible { + @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2; +} +``` + +### Color Contrast +```css +/* Ensure WCAG 2.1 AA compliance */ +.text-primary { + @apply text-slate-900 dark:text-slate-100; +} + +.text-secondary { + @apply text-slate-600 dark:text-slate-400; +} +``` + +## Naming Conventions + +### CSS Classes +- Use kebab-case: `btn-primary`, `form-input` +- Prefix utilities with `u-`: `u-flex`, `u-text-sm` +- State modifiers: `is-active`, `has-error` + +### JavaScript/React +- Components: PascalCase (`UserProfile.jsx`) +- Utilities: camelCase (`formatDate.js`) +- Constants: UPPER_SNAKE_CASE (`API_ENDPOINTS`) + +### File Structure +``` +app/javascript/ +├── components/ +│ ├── ui/ # Reusable UI components +│ ├── forms/ # Form-specific components +│ └── layouts/ # Layout components +├── lib/ # Utilities and helpers +└── controllers/ # Stimulus controllers +``` \ No newline at end of file