Compare commits
3 Commits
6b37c67b47
...
c226adc36c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c226adc36c | ||
|
|
0879b3c924 | ||
|
|
884c6a8262 |
64
.editorconfig
Normal file
64
.editorconfig
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
# Change these settings to your own preference
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# We recommend you to have these uncommented (set to true).
|
||||||
|
# If you want to support older versions of Ruby, set this to 1.9
|
||||||
|
# ruby_version = 2.7
|
||||||
|
# If you want to support older versions of JavaScript, set this to 5
|
||||||
|
# javascript_version = 6
|
||||||
|
|
||||||
|
# Extend from global settings
|
||||||
|
[*.{rb,erb}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{js,jsx}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{json,json5,jsonc}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{css,scss,less}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{html,htm,erb}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{md,markdown}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
@@ -29,6 +29,15 @@ SMTP_PORT=1025
|
|||||||
SMTP_AUTHENTICATION=plain
|
SMTP_AUTHENTICATION=plain
|
||||||
SMTP_ENABLE_STARTTLS=false
|
SMTP_ENABLE_STARTTLS=false
|
||||||
|
|
||||||
|
# Production SMTP Configuration (set these in .env.production)
|
||||||
|
# SMTP_ADDRESS=smtp.example.com
|
||||||
|
# SMTP_PORT=587
|
||||||
|
# SMTP_USERNAME=your_smtp_username
|
||||||
|
# SMTP_PASSWORD=your_smtp_password
|
||||||
|
# SMTP_AUTHENTICATION=plain
|
||||||
|
# SMTP_DOMAIN=example.com
|
||||||
|
# SMTP_STARTTLS=true
|
||||||
|
|
||||||
# Application variables
|
# Application variables
|
||||||
STRIPE_API_KEY=1337
|
STRIPE_API_KEY=1337
|
||||||
|
|
||||||
|
|||||||
1
Gemfile
1
Gemfile
@@ -74,5 +74,6 @@ gem "devise", "~> 4.9"
|
|||||||
|
|
||||||
# Pagination gem
|
# Pagination gem
|
||||||
gem "kaminari", "~> 1.2"
|
gem "kaminari", "~> 1.2"
|
||||||
|
gem "kaminari-tailwind", "~> 0.1.0"
|
||||||
|
|
||||||
# gem "net-pop", "~> 0.1.2"
|
# gem "net-pop", "~> 0.1.2"
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ GEM
|
|||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.2.2)
|
kaminari-core (= 1.2.2)
|
||||||
kaminari-core (1.2.2)
|
kaminari-core (1.2.2)
|
||||||
|
kaminari-tailwind (0.1.0)
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
logger (1.7.0)
|
logger (1.7.0)
|
||||||
@@ -399,6 +400,7 @@ DEPENDENCIES
|
|||||||
jsbundling-rails
|
jsbundling-rails
|
||||||
kamal
|
kamal
|
||||||
kaminari (~> 1.2)
|
kaminari (~> 1.2)
|
||||||
|
kaminari-tailwind (~> 0.1.0)
|
||||||
minitest-reporters (~> 1.7)
|
minitest-reporters (~> 1.7)
|
||||||
mysql2 (~> 0.5)
|
mysql2 (~> 0.5)
|
||||||
propshaft
|
propshaft
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
web: env RUBY_DEBUG_OPEN=true bin/rails server
|
web: env RUBY_DEBUG_OPEN=true bin/rails server
|
||||||
js: yarn build --watch
|
js: yarn build:dev --watch
|
||||||
css: yarn build:css --watch
|
css: yarn build:css --watch
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
/* Import Tailwind using PostCSS */
|
/* Import Tailwind using PostCSS */
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* Import flash message styles */
|
||||||
|
@import "components/flash";
|
||||||
|
|
||||||
/** Default text color */
|
/** Default text color */
|
||||||
body {
|
body {
|
||||||
color: #555555;
|
color: #555555;
|
||||||
|
|||||||
39
app/assets/stylesheets/components/flash.css
Normal file
39
app/assets/stylesheets/components/flash.css
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* Flash Messages - Theme Integration */
|
||||||
|
.flash-message {
|
||||||
|
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles for all flash messages */
|
||||||
|
.flash-message .flex {
|
||||||
|
@apply rounded-md p-4 border shadow-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Success message styles */
|
||||||
|
.flash-message-success {
|
||||||
|
@apply bg-green-50 border-green-100 text-green-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error message styles */
|
||||||
|
.flash-message-error {
|
||||||
|
@apply bg-red-50 border-red-100 text-red-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning message styles */
|
||||||
|
.flash-message-warning {
|
||||||
|
@apply bg-yellow-50 border-yellow-100 text-yellow-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info message styles */
|
||||||
|
.flash-message-info {
|
||||||
|
@apply bg-blue-50 border-blue-100 text-blue-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notice message styles */
|
||||||
|
.flash-message-notice {
|
||||||
|
@apply bg-purple-50 border-purple-100 text-purple-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert message styles */
|
||||||
|
.flash-message-alert {
|
||||||
|
@apply bg-red-50 border-red-100 text-red-800;
|
||||||
|
}
|
||||||
@@ -23,9 +23,11 @@ class Authentications::PasswordsController < Devise::PasswordsController
|
|||||||
|
|
||||||
# protected
|
# protected
|
||||||
|
|
||||||
# def after_resetting_password_path_for(resource)
|
# Override to set a flash message on successful password reset
|
||||||
# super(resource)
|
def after_resetting_password_path_for(resource)
|
||||||
# end
|
flash[:notice] = "Votre mot de passe a été changé avec succès. Vous êtes maintenant connecté."
|
||||||
|
super(resource)
|
||||||
|
end
|
||||||
|
|
||||||
# The path used after sending reset password instructions
|
# The path used after sending reset password instructions
|
||||||
# def after_sending_reset_password_instructions_path_for(resource_name)
|
# def after_sending_reset_password_instructions_path_for(resource_name)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Authentications::RegistrationsController < Devise::RegistrationsController
|
class Authentications::RegistrationsController < Devise::RegistrationsController
|
||||||
# before_action :configure_sign_up_params, only: [:create]
|
before_action :configure_sign_up_params, only: [ :create ]
|
||||||
# before_action :configure_account_update_params, only: [:update]
|
before_action :configure_account_update_params, only: [ :update ]
|
||||||
|
|
||||||
# GET /resource/sign_up
|
# GET /resource/sign_up
|
||||||
# def new
|
# def new
|
||||||
@@ -41,14 +41,14 @@ class Authentications::RegistrationsController < Devise::RegistrationsController
|
|||||||
# protected
|
# protected
|
||||||
|
|
||||||
# If you have extra params to permit, append them to the sanitizer.
|
# If you have extra params to permit, append them to the sanitizer.
|
||||||
# def configure_sign_up_params
|
def configure_sign_up_params
|
||||||
# devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
|
devise_parameter_sanitizer.permit(:sign_up, keys: [ :last_name, :first_name ])
|
||||||
# end
|
end
|
||||||
|
|
||||||
# If you have extra params to permit, append them to the sanitizer.
|
# If you have extra params to permit, append them to the sanitizer.
|
||||||
# def configure_account_update_params
|
def configure_account_update_params
|
||||||
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
|
devise_parameter_sanitizer.permit(:account_update, keys: [ :last_name, :first_name ])
|
||||||
# end
|
end
|
||||||
|
|
||||||
# The path used after sign up.
|
# The path used after sign up.
|
||||||
# def after_sign_up_path_for(resource)
|
# def after_sign_up_path_for(resource)
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ class Authentications::SessionsController < Devise::SessionsController
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
# POST /resource/sign_in
|
# POST /resource/sign_in
|
||||||
# def create
|
def create
|
||||||
# super
|
super
|
||||||
# end
|
flash[:notice] = "Connexion réussie !" if resource.persisted?
|
||||||
|
end
|
||||||
|
|
||||||
# DELETE /resource/sign_out
|
# DELETE /resource/sign_out
|
||||||
# def destroy
|
# def destroy
|
||||||
|
|||||||
@@ -3,17 +3,26 @@
|
|||||||
class PagesController < ApplicationController
|
class PagesController < ApplicationController
|
||||||
# Skip authentication for public pages
|
# Skip authentication for public pages
|
||||||
# skip_before_action :authenticate_user!, only: [ :home ]
|
# skip_before_action :authenticate_user!, only: [ :home ]
|
||||||
|
before_action :authenticate_user!, only: [ :dashboard ]
|
||||||
|
|
||||||
# Homepage showing featured parties
|
# Homepage showing featured parties
|
||||||
def home
|
def home
|
||||||
# @parties = Party.published.featured.limit(3)
|
# @parties = Party.published.featured.limit(3)
|
||||||
@parties = Party.where(state: :published).order(created_at: :desc)
|
@parties = Party.where(state: :published).order(created_at: :desc)
|
||||||
puts @parties
|
|
||||||
|
if user_signed_in?
|
||||||
|
return redirect_to(dashboard_path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# User dashboard showing personalized content
|
# User dashboard showing personalized content
|
||||||
# Accessible only to authenticated users
|
# Accessible only to authenticated users
|
||||||
def dashboard
|
def dashboard
|
||||||
|
@available_parties = Party.published.count
|
||||||
|
@events_this_week = Party.published.where("start_time BETWEEN ? AND ?", Date.current.beginning_of_week, Date.current.end_of_week).count
|
||||||
|
@today_parties = Party.published.where("DATE(start_time) = ?", Date.current).order(start_time: :asc)
|
||||||
|
@tomorrow_parties = Party.published.where("DATE(start_time) = ?", Date.current + 1).order(start_time: :asc)
|
||||||
|
@other_parties = Party.published.upcoming.where.not("DATE(start_time) IN (?)", [Date.current, Date.current + 1]).order(start_time: :asc).page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Events page showing all published parties with pagination
|
# Events page showing all published parties with pagination
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
|
# Convert prince from cents to float
|
||||||
def format_price(cents)
|
def format_price(cents)
|
||||||
(cents.to_f / 100).round(2)
|
(cents.to_f / 100).round(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Include flash message helpers
|
||||||
|
include FlashMessagesHelper
|
||||||
end
|
end
|
||||||
|
|||||||
34
app/helpers/flash_messages_helper.rb
Normal file
34
app/helpers/flash_messages_helper.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module FlashMessagesHelper
|
||||||
|
def flash_class(type)
|
||||||
|
case type.to_s
|
||||||
|
when 'notice' then 'flash-message-success'
|
||||||
|
when 'success' then 'flash-message-success'
|
||||||
|
when 'error' then 'flash-message-error'
|
||||||
|
when 'alert' then 'flash-message-error'
|
||||||
|
when 'warning' then 'flash-message-warning'
|
||||||
|
when 'info' then 'flash-message-info'
|
||||||
|
else "flash-message-#{type}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def flash_icon(type)
|
||||||
|
case type.to_s
|
||||||
|
when 'notice', 'success'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-green-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
when 'error', 'alert'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-red-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
when 'warning'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-blue-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
27
app/javascript/controllers/flash_message_controller.js
Normal file
27
app/javascript/controllers/flash_message_controller.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["message"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log("FlashMessageController mounted", this.element);
|
||||||
|
|
||||||
|
// Auto-dismiss after 5 seconds
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.close()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.element.classList.add('opacity-0', 'transition-opacity', 'duration-300')
|
||||||
|
setTimeout(() => {
|
||||||
|
this.element.remove()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,13 @@
|
|||||||
|
|
||||||
import { application } from "./application"
|
import { application } from "./application"
|
||||||
|
|
||||||
import ShadcnTestController from "./shadcn_test_controller"
|
import LogoutController from "./logout_controller"
|
||||||
|
import FlashMessage from "./flash_message_controller"
|
||||||
import CounterController from "./counter_controller"
|
import CounterController from "./counter_controller"
|
||||||
|
import ShadcnTestController from "./shadcn_test_controller"
|
||||||
|
|
||||||
application.register("shadcn-test", ShadcnTestController)
|
application.register("logout", LogoutController) // Allow logout using js
|
||||||
application.register("counter", CounterController)
|
application.register("flash-message", FlashMessage) // Dismiss notification after 5 secondes
|
||||||
|
application.register("counter", CounterController) // Simple counter for homepage
|
||||||
|
|
||||||
|
application.register("shadcn-test", ShadcnTestController) // Test controller for Shadcn
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// app/javascript/controllers/logout_controller.js
|
|
||||||
import { Controller } from "@hotwired/stimulus";
|
import { Controller } from "@hotwired/stimulus";
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
@@ -7,14 +6,13 @@ export default class extends Controller {
|
|||||||
};
|
};
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
// Optional: Add confirmation message
|
// Display a message when the controller is mounted
|
||||||
//console.log("Hello LogoutController, Stimulus!", this.element);
|
console.log("LogoutController mounted", this.element);
|
||||||
// this.element.dataset.confirm = "Êtes-vous sûr de vouloir vous déconnecter ?";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signOut(event) {
|
signOut(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log("LogoutController#signOut mounted");
|
console.log("User clicked on logout button.");
|
||||||
|
|
||||||
// Ensure user wants to disconnect with a confirmation request
|
// Ensure user wants to disconnect with a confirmation request
|
||||||
// if (this.hasUrlValue && !confirm(this.element.dataset.confirm)) { return; }
|
// if (this.hasUrlValue && !confirm(this.element.dataset.confirm)) { return; }
|
||||||
@@ -23,7 +21,11 @@ export default class extends Controller {
|
|||||||
const csrfToken = document.querySelector("[name='csrf-token']").content;
|
const csrfToken = document.querySelector("[name='csrf-token']").content;
|
||||||
|
|
||||||
// Define url to redirect user when action is valid
|
// Define url to redirect user when action is valid
|
||||||
const url = this.hasUrlValue ? this.urlValue : this.element.href;
|
let url = this.hasUrlValue ? this.urlValue : this.element.href;
|
||||||
|
// Ensure the URL is using the correct path prefix
|
||||||
|
if (url && !url.includes('/auth/sign_out')) {
|
||||||
|
url = url.replace('/users/sign_out', '/auth/sign_out');
|
||||||
|
}
|
||||||
|
|
||||||
// Use fetch to send logout request
|
// Use fetch to send logout request
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
|
|||||||
@@ -22,4 +22,8 @@ class User < ApplicationRecord
|
|||||||
# Relationships
|
# Relationships
|
||||||
has_many :parties, dependent: :destroy
|
has_many :parties, dependent: :destroy
|
||||||
has_many :tickets, dependent: :destroy
|
has_many :tickets, dependent: :destroy
|
||||||
|
|
||||||
|
# Validations
|
||||||
|
validates :last_name, length: { minimum: 3, maximum: 12, allow_blank: true }
|
||||||
|
validates :first_name, length: { minimum: 3, maximum: 12, allow_blank: true }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,122 +1,141 @@
|
|||||||
<header class="shadow-sm border-b border-neutral-200">
|
<header class="shadow-sm border-b border-neutral-200">
|
||||||
<div class="bg-gray-800">
|
<div class="bg-gray-800">
|
||||||
<nav x-data="{ open: false }" class="bg-blue border-b border-purple-700">
|
<nav x-data="{ open: false }" class="bg-blue border-b border-purple-700">
|
||||||
<!-- Primary Navigation Menu -->
|
<!-- Primary Navigation Menu -->
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex justify-between h-16">
|
<div class="flex justify-between h-16">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="shrink-0 flex items-center">
|
<div class="shrink-0 flex items-center">
|
||||||
<%= link_to Rails.application.config.app_name, "/", class: "text-xl font-bold text-white" %>
|
<%= link_to Rails.application.config.app_name, "/" , class: "text-xl font-bold text-white" %>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation Links -->
|
|
||||||
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex items-center">
|
|
||||||
<%= link_to "Soirées et afterworks", parties_path, class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200" %>
|
|
||||||
<%= link_to "Concerts", "#", class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200" %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Navigation Links -->
|
||||||
<!-- Authentication Links -->
|
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex items-center">
|
||||||
<% if user_signed_in? %>
|
<%= link_to "Soirées et afterworks" , parties_path,
|
||||||
<!-- Settings Dropdown -->
|
class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
|
||||||
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
%>
|
||||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
<%= link_to "Concerts" , "#" ,
|
||||||
<div @click="open = ! open">
|
class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
|
||||||
<button class="bg-purple-700 text-white border border-purple-800 font-medium py-2 px-4 rounded-lg hover:bg-purple-800 transition-colors duration-200 focus-ring">
|
%>
|
||||||
<div><%= current_user.email %></div>
|
|
||||||
<div class="ms-1">
|
|
||||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div x-show="open"
|
|
||||||
x-transition:enter="transition ease-out duration-200"
|
|
||||||
x-transition:enter-start="opacity-0 scale-95"
|
|
||||||
x-transition:enter-end="opacity-100 scale-100"
|
|
||||||
x-transition:leave="transition ease-in duration-75"
|
|
||||||
x-transition:leave-start="opacity-100 scale-100"
|
|
||||||
x-transition:leave-end="opacity-0 scale-95"
|
|
||||||
class="absolute z-50 mt-2 w-48 rounded-md shadow-lg origin-top-right right-0"
|
|
||||||
style="display: none;"
|
|
||||||
@click="open = false">
|
|
||||||
<div class="rounded-md ring-1 ring-purple-700 py-1 bg-purple-600">
|
|
||||||
<%= link_to "Mon profil", edit_user_registration_path, class: "block w-full px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
|
||||||
<%= link_to "Mes réservations", "#", class: "block w-full px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
|
||||||
|
|
||||||
<%= 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 px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<!-- Login/Register Links -->
|
|
||||||
<div class="hidden sm:flex sm:items-center sm:ms-6 space-x-4 items-center">
|
|
||||||
<%= link_to "S'inscrire", new_user_registration_path, class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200" %>
|
|
||||||
<%= link_to "Se connecter", new_user_session_path, class: "bg-white text-purple-600 font-medium py-2 px-4 rounded-lg shadow-sm hover:bg-purple-100 transition-all duration-200" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<!-- Hamburger -->
|
|
||||||
<div class="-me-2 flex items-center sm:hidden">
|
|
||||||
<button @click="open = ! open" class="p-2 rounded-md text-purple-200 hover:text-white hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
|
||||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path :class="{ 'hidden': open, 'inline-flex': !open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
||||||
<path :class="{ 'hidden': !open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Authentication Links -->
|
||||||
|
<% if user_signed_in? %>
|
||||||
|
<!-- Settings Dropdown -->
|
||||||
|
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
||||||
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||||
|
<div @click="open = ! open">
|
||||||
|
<button
|
||||||
|
class="bg-purple-700 text-white border border-purple-800 font-medium py-2 px-4 rounded-lg hover:bg-purple-800 transition-colors duration-200 focus-ring">
|
||||||
|
<div>
|
||||||
|
<%= current_user.email.length> 20 ? current_user.email[0,20] + "..." : current_user.email %>
|
||||||
|
</div>
|
||||||
|
<div class="ms-1">
|
||||||
|
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div x-show="open" x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
class="absolute z-50 mt-2 w-48 rounded-md shadow-lg origin-top-right right-0" style="display: none;"
|
||||||
|
@click="open = false">
|
||||||
|
<div class="rounded-md ring-1 ring-purple-700 py-1 bg-purple-600">
|
||||||
|
<%= link_to "Mon profil" , edit_user_registration_path,
|
||||||
|
class: "block w-full px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
||||||
|
<%= link_to "Mes réservations" , "#" ,
|
||||||
|
class: "block w-full px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
||||||
|
<%= 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 px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<!-- Login/Register Links -->
|
||||||
|
<div class="hidden sm:flex sm:items-center sm:ms-6 space-x-4 items-center">
|
||||||
|
<%= link_to "Se connecter" , new_user_session_path,
|
||||||
|
class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
|
||||||
|
%>
|
||||||
|
<%= link_to "S'inscrire" , new_user_registration_path,
|
||||||
|
class: "bg-white text-purple-600 font-medium py-2 px-4 rounded-lg shadow-sm hover:bg-purple-100 transition-all duration-200"
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<!-- Hamburger -->
|
||||||
|
<div class="-me-2 flex items-center sm:hidden">
|
||||||
|
<button @click="open = ! open"
|
||||||
|
class="p-2 rounded-md text-purple-200 hover:text-white hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
||||||
|
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path :class="{ 'hidden': open, 'inline-flex': !open }" class="inline-flex" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
<path :class="{ 'hidden': !open, 'inline-flex': open }" class="hidden" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Responsive Navigation Menu -->
|
<!-- Responsive Navigation Menu -->
|
||||||
<div :class="{ 'block': open, 'hidden': !open }" class="hidden sm:hidden">
|
<div :class="{ 'block': open, 'hidden': !open }" class="hidden sm:hidden">
|
||||||
<div class="pt-2 pb-3 space-y-1 bg-purple-600">
|
<div class="pt-2 pb-3 space-y-1 bg-purple-600">
|
||||||
<%= link_to "Soirées et afterworks", "#", class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
<%= link_to "Soirées et afterworks" , "#" ,
|
||||||
<%= link_to "Concerts", "#", class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
</div>
|
%>
|
||||||
|
<%= link_to "Concerts" , "#" ,
|
||||||
<!-- Responsive Settings Options -->
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
<div class="pt-4 pb-1 border-t border-purple-700 bg-purple-600">
|
%>
|
||||||
<% if user_signed_in? %>
|
|
||||||
<div class="px-4">
|
|
||||||
<div class="font-medium text-base text-white"><%= current_user.email %></div>
|
|
||||||
<div class="font-medium text-sm text-purple-200"><%= current_user.email %></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<%= link_to "Mon profil", edit_user_registration_path, class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
|
||||||
<%= link_to "Mes réservations", "#", class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
|
||||||
|
|
||||||
<%= 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 px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<%= link_to "S'inscrire", new_user_registration_path, class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
|
||||||
<%= link_to "Se connecter", new_user_session_path, class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
<!-- Responsive Settings Options -->
|
||||||
|
<div class="pt-4 pb-1 border-t border-purple-700 bg-purple-600">
|
||||||
|
<% if user_signed_in? %>
|
||||||
|
<div class="px-4">
|
||||||
|
<% if current_user.first_name %>
|
||||||
|
<div class="font-medium text-base text-white">
|
||||||
|
<%= current_user.first_name %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="font-medium text-base text-white">
|
||||||
|
<%= current_user.email.length> 20 ? current_user.email[0,20] + "..." : current_user.email %>
|
||||||
|
</div>
|
||||||
|
<%# <div class="font-medium text-sm text-purple-200">
|
||||||
|
<%= current_user.email.length> 20 ? current_user.email[0,20] + "..." : current_user.email %>
|
||||||
|
</div>
|
||||||
|
%>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 space-y-1">
|
||||||
|
<%= link_to "Mon profil" , edit_user_registration_path,
|
||||||
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
|
%>
|
||||||
|
<%= link_to "Mes réservations" , "#" ,
|
||||||
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
|
%>
|
||||||
|
|
||||||
|
<%= 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 px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="mt-3 space-y-1">
|
||||||
|
<%= link_to "S'inscrire" , new_user_registration_path,
|
||||||
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
|
%>
|
||||||
|
<%= link_to "Se connecter" , new_user_session_path,
|
||||||
|
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<h2>Resend confirmation instructions</h2>
|
<h2>Resend confirmation instructions</h2>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<%= f.label :email %><br />
|
<%= f.label :email %><br />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="min-h-screen flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
<div class="flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-md w-full space-y-8">
|
<div class="max-w-md w-full space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<%= link_to "/" do %>
|
<%= link_to "/" do %>
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "mt-8 space-y-6" }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "mt-8 space-y-6" }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
<%= f.hidden_field :reset_password_token %>
|
<%= f.hidden_field :reset_password_token %>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -22,19 +21,19 @@
|
|||||||
<% if @minimum_password_length %>
|
<% if @minimum_password_length %>
|
||||||
<em class="text-sm text-neutral-500">(<%= @minimum_password_length %> caractères minimum)</em>
|
<em class="text-sm text-neutral-500">(<%= @minimum_password_length %> caractères minimum)</em>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= f.password_field :password, autofocus: true, autocomplete: "new-password",
|
<%= f.password_field :password, autofocus: true, autocomplete: "new-password",
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :password_confirmation, "Confirmer le nouveau mot de passe", class: "block text-sm font-medium text-neutral-700" %>
|
<%= f.label :password_confirmation, "Confirmer le nouveau mot de passe", class: "block text-sm font-medium text-neutral-700" %>
|
||||||
<%= f.password_field :password_confirmation, autocomplete: "new-password",
|
<%= f.password_field :password_confirmation, autocomplete: "new-password",
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<%= f.submit "Changer mon mot de passe",
|
<%= f.submit "Changer mon mot de passe",
|
||||||
class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-50 focus:ring-purple-500" %>
|
class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-50 focus:ring-purple-500" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="min-h-screen flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
<div class="flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-md w-full space-y-8">
|
<div class="max-w-md w-full space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<%= link_to "/" do %>
|
<%= link_to "/" do %>
|
||||||
@@ -13,19 +13,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: "mt-8 space-y-6" }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: "mt-8 space-y-6" }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
|
<%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
||||||
class: "appearance-none block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm",
|
class: "appearance-none block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm",
|
||||||
placeholder: "Adresse email" %>
|
placeholder: "Adresse email" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.submit "Envoyer le lien de réinitialisation",
|
<%= f.submit "Envoyer le lien de réinitialisation",
|
||||||
class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-50 focus:ring-purple-500" %>
|
class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-50 focus:ring-purple-500" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="min-h-screen flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
<div class="flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-md w-full space-y-8">
|
<div class="max-w-md w-full space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<%= link_to "/" do %>
|
<%= link_to "/" do %>
|
||||||
@@ -16,12 +16,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "mt-8 space-y-6" }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "mt-8 space-y-6" }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
|
<%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
|
||||||
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,13 +29,13 @@
|
|||||||
<% if @minimum_password_length %>
|
<% if @minimum_password_length %>
|
||||||
<em class="text-sm text-neutral-500">(<%= @minimum_password_length %> caractères minimum)</em>
|
<em class="text-sm text-neutral-500">(<%= @minimum_password_length %> caractères minimum)</em>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= f.password_field :password, autocomplete: "new-password",
|
<%= f.password_field :password, autocomplete: "new-password",
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :password_confirmation, class: "block text-sm font-medium text-neutral-700" %>
|
<%= f.label :password_confirmation, class: "block text-sm font-medium text-neutral-700" %>
|
||||||
<%= f.password_field :password_confirmation, autocomplete: "new-password",
|
<%= f.password_field :password_confirmation, autocomplete: "new-password",
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
class: "mt-1 block w-full px-3 py-2 border border-neutral-300 rounded-md shadow-sm placeholder-neutral-500 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +55,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render "devise/shared/links" %>
|
<div class="mt-4">
|
||||||
|
<%= render "devise/shared/links" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="min-h-screen flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
<div class="flex items-center justify-center bg-neutral-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-md w-full space-y-8">
|
<div class="max-w-md w-full space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<%= link_to "/" do %>
|
<%= link_to "/" do %>
|
||||||
@@ -16,20 +16,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "mt-8 space-y-6" }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "mt-8 space-y-6" }) do |f| %>
|
||||||
<%= devise_error_messages! %>
|
|
||||||
|
|
||||||
<div class="rounded-md shadow-sm -space-y-px">
|
<div class="rounded-md shadow-sm -space-y-px">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<%= f.label :email, class: "sr-only" %>
|
<%= f.label :email, class: "sr-only" %>
|
||||||
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
<%= f.email_field :email, autofocus: true, autocomplete: "email",
|
||||||
class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-neutral-300 placeholder-neutral-500 text-neutral-900 bg-white rounded-t-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm",
|
class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-neutral-300 placeholder-neutral-500 text-neutral-900 bg-white rounded-t-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm",
|
||||||
placeholder: "Adresse email" %>
|
placeholder: "Adresse email" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<%= f.label :password, class: "sr-only" %>
|
<%= f.label :password, class: "sr-only" %>
|
||||||
<%= f.password_field :password, autocomplete: "current-password",
|
<%= f.password_field :password, autocomplete: "current-password",
|
||||||
class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-neutral-300 placeholder-neutral-500 text-neutral-900 bg-white rounded-b-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm",
|
class: "appearance-none rounded-none relative block w-full px-3 py-2 border border-neutral-300 placeholder-neutral-500 text-neutral-900 bg-white rounded-b-md focus:outline-none focus:ring-purple-500 focus:border-purple-500 focus:z-10 sm:text-sm",
|
||||||
placeholder: "Mot de passe" %>
|
placeholder: "Mot de passe" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
<% if resource.errors.any? %>
|
<% if resource.errors.any? %>
|
||||||
<div id="error_explanation" data-turbo-cache="false" class="bg-red-50 border border-red-200 rounded-md p-4 mb-4">
|
<% resource.errors.full_messages.each do |message| %>
|
||||||
<h2 class="text-lg font-medium text-red-800 mb-3">
|
<% flash.now[:error] = message %>
|
||||||
<%= I18n.t("errors.messages.not_saved",
|
<% end %>
|
||||||
count: resource.errors.count,
|
|
||||||
resource: resource.class.model_name.human.downcase) %>
|
|
||||||
</h2>
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<% resource.errors.full_messages.each do |message| %>
|
|
||||||
<li class="text-sm text-red-700"><%= message %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<h2>Resend unlock instructions</h2>
|
<h2>Resend unlock instructions</h2>
|
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<%= f.label :email %><br />
|
<%= f.label :email %><br />
|
||||||
|
|||||||
13
app/views/kaminari/_first_page.html.erb
Normal file
13
app/views/kaminari/_first_page.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<%# Link to the "First" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the first page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url,
|
||||||
|
class: "px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors duration-200 shadow-sm hover:shadow-md",
|
||||||
|
remote: remote %>
|
||||||
|
</li>
|
||||||
12
app/views/kaminari/_gap.html.erb
Normal file
12
app/views/kaminari/_gap.html.erb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<%# Non-link tag that stands for skipped pages...
|
||||||
|
- available local variables
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<span class="px-3 py-2 text-sm font-medium text-gray-400 bg-transparent">
|
||||||
|
<%= t('views.pagination.truncate').html_safe %>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
13
app/views/kaminari/_last_page.html.erb
Normal file
13
app/views/kaminari/_last_page.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<%# Link to the "Last" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the last page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url,
|
||||||
|
class: "px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors duration-200 shadow-sm hover:shadow-md",
|
||||||
|
remote: remote %>
|
||||||
|
</li>
|
||||||
13
app/views/kaminari/_next_page.html.erb
Normal file
13
app/views/kaminari/_next_page.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<%# Link to the "Next" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the next page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url,
|
||||||
|
class: "px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors duration-200 shadow-sm hover:shadow-md",
|
||||||
|
rel: 'next', remote: remote %>
|
||||||
|
</li>
|
||||||
20
app/views/kaminari/_page.html.erb
Normal file
20
app/views/kaminari/_page.html.erb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<%# Link showing page number
|
||||||
|
- available local variables
|
||||||
|
page: a page object for "this" page
|
||||||
|
url: url to this page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<% if page.current? %>
|
||||||
|
<span class="px-3 py-2 text-sm font-medium text-white bg-indigo-600 border border-indigo-600 rounded shadow-md" aria-current="page">
|
||||||
|
<%= page %>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to page, url,
|
||||||
|
class: "px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors duration-200 shadow-sm hover:shadow-md",
|
||||||
|
remote: remote, rel: page.rel %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
27
app/views/kaminari/_paginator.html.erb
Normal file
27
app/views/kaminari/_paginator.html.erb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<%# The container tag
|
||||||
|
- available local variables
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
paginator: the paginator that renders the pagination tags inside
|
||||||
|
-%>
|
||||||
|
<%= paginator.render do -%>
|
||||||
|
<nav class="flex justify-center mt-8 mb-4" role="navigation" aria-label="pager">
|
||||||
|
<ul class="flex flex-wrap items-center justify-center gap-2">
|
||||||
|
<%= first_page_tag unless current_page.first? %>
|
||||||
|
<%= prev_page_tag unless current_page.first? %>
|
||||||
|
<% each_page do |page| -%>
|
||||||
|
<% if page.display_tag? -%>
|
||||||
|
<%= page_tag page %>
|
||||||
|
<% elsif !page.was_truncated? -%>
|
||||||
|
<%= gap_tag %>
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
<% unless current_page.out_of_range? %>
|
||||||
|
<%= next_page_tag unless current_page.last? %>
|
||||||
|
<%= last_page_tag unless current_page.last? %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<% end -%>
|
||||||
13
app/views/kaminari/_prev_page.html.erb
Normal file
13
app/views/kaminari/_prev_page.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<%# Link to the "Previous" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the previous page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<li>
|
||||||
|
<%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url,
|
||||||
|
class: "px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-100 hover:text-gray-700 transition-colors duration-200 shadow-sm hover:shadow-md",
|
||||||
|
rel: 'prev', remote: remote %>
|
||||||
|
</li>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<nav aria-label="Page navigation">
|
|
||||||
<ul class="pagination">
|
|
||||||
<% if paginator.prev_page %>
|
|
||||||
<li class="page-item">
|
|
||||||
<%= link_to 'Previous', url_for(page: paginator.prev_page), class: 'page-link' %>
|
|
||||||
</li>
|
|
||||||
<% else %>
|
|
||||||
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% paginator.page_range.each do |page| %>
|
|
||||||
<li class="page-item <%= 'active' if page == paginator.current_page %>">
|
|
||||||
<%= link_to page, url_for(page: page), class: 'page-link' %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if paginator.next_page %>
|
|
||||||
<li class="page-item">
|
|
||||||
<%= link_to 'Next', url_for(page: paginator.next_page), class: 'page-link' %>
|
|
||||||
</li>
|
|
||||||
<% else %>
|
|
||||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
@@ -7,28 +7,30 @@
|
|||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<%= csp_meta_tag %>
|
<%= csp_meta_tag %>
|
||||||
|
|
||||||
<%= yield :head %>
|
<%= yield :head %>
|
||||||
|
|
||||||
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
||||||
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
|
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
|
||||||
|
|
||||||
<link rel="icon" href="/icon.png" type="image/png">
|
<link rel="icon" href="/icon.png" type="image/png">
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||||
<link rel="apple-touch-icon" href="/icon.png">
|
<link rel="apple-touch-icon" href="/icon.png">
|
||||||
|
|
||||||
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
||||||
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
||||||
<%= stylesheet_link_tag "theme", "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag "theme", "data-turbo-track": "reload" %>
|
||||||
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
|
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="h-full font-sans text-neutral-900 antialiased">
|
<body class="h-full font-sans text-neutral-900 antialiased">
|
||||||
<div class="min-h-full">
|
|
||||||
<%= render "components/header" %>
|
<div class="">
|
||||||
|
<%= render "components/header" %>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<main class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<%= yield %>
|
<div class="flash">
|
||||||
|
<%= render "shared/flash_messages" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="yield">
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="bg-neutral-100 text-neutral-600">
|
<footer class="bg-neutral-100 text-neutral-600">
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
<%= render "components/footer" %>
|
<%= render "components/footer" %>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
157
app/views/pages/dashboard.html.erb
Normal file
157
app/views/pages/dashboard.html.erb
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<!-- Hero section with metrics -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-6">Tableau de bord</h1>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
|
||||||
|
<div class="group relative overflow-hidden rounded-2xl bg-white dark:bg-slate-800 border border-neutral-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 transition-all duration-300 p-8">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 dark:from-purple-900/20 dark:to-indigo-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="text-5xl md:text-3xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
|
||||||
|
<%= @available_parties %>
|
||||||
|
</div>
|
||||||
|
<p class="text-neutral-700 dark:text-neutral-300 font-mono uppercase tracking-widest text-sm font-medium">
|
||||||
|
Événements disponibles
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 h-1 bg-gradient-to-r from-purple-500 via-indigo-500 to-pink-500 rounded-full w-0 group-hover:w-full transition-all duration-500"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group relative overflow-hidden rounded-2xl bg-white dark:bg-slate-800 border border-neutral-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 transition-all duration-300 p-8">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 dark:from-purple-900/20 dark:to-indigo-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="text-5xl md:text-3xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
|
||||||
|
<%= @events_this_week %>
|
||||||
|
</div>
|
||||||
|
<p class="text-neutral-700 dark:text-neutral-300 font-mono uppercase tracking-widest text-sm font-medium">
|
||||||
|
Événements aujourd'hui
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 h-1 bg-gradient-to-r from-purple-500 via-indigo-500 to-pink-500 rounded-full w-0 group-hover:w-full transition-all duration-500"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="group relative overflow-hidden rounded-2xl bg-white dark:bg-slate-800 border border-neutral-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 transition-all duration-300 p-8">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 dark:from-purple-900/20 dark:to-indigo-900/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="text-5xl md:text-3xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
|
||||||
|
<%= @events_this_week %>
|
||||||
|
</div>
|
||||||
|
<p class="text-neutral-700 dark:text-neutral-300 font-mono uppercase tracking-widest text-sm font-medium">
|
||||||
|
Événements cette semaine
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 h-1 bg-gradient-to-r from-purple-500 via-indigo-500 to-pink-500 rounded-full w-0 group-hover:w-full transition-all duration-500"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Today's parties -->
|
||||||
|
<div class="card hover-lift mb-8">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Évenements du jour</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<% if @today_parties.any? %>
|
||||||
|
<ul class="space-y-4">
|
||||||
|
<% @today_parties.each do |party| %>
|
||||||
|
<li>
|
||||||
|
<%= link_to party_path(party.slug, party), 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="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
||||||
|
<%= image_tag party.image, alt: party.name, class: "w-full h-full object-cover" if party.image.present? %>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<%= party.name %>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
<%= l(party.start_time, format: :short) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-slate-600 dark:text-slate-400">Aucune partie aujourd'hui.</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tomorrow's parties -->
|
||||||
|
<div class="card hover-lift mb-8">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Évenements de demain</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<% if @tomorrow_parties.any? %>
|
||||||
|
<ul class="space-y-4">
|
||||||
|
<% @tomorrow_parties.each do |party| %>
|
||||||
|
<li>
|
||||||
|
<%= link_to party_path(party.slug, party), 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="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
||||||
|
<%= image_tag party.image, alt: party.name, class: "w-full h-full object-cover" if party.image.present? %>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<%= party.name %>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
<%= l(party.start_time, format: :short) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-slate-600 dark:text-slate-400">Aucune partie demain.</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Other upcoming parties with pagination -->
|
||||||
|
<div class="card hover-lift">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Autres évenements à venir</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<% if @other_parties.any? %>
|
||||||
|
<ul class="space-y-4">
|
||||||
|
<% @other_parties.each do |party| %>
|
||||||
|
<li>
|
||||||
|
<%= link_to party_path(party.slug, party), 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="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
||||||
|
<%= image_tag party.image, alt: party.name, class: "w-full h-full object-cover" if party.image.present? %>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<%= party.name %>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
<%= l(party.start_time, format: :short) %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<%= paginate @other_parties %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-slate-600 dark:text-slate-400">Aucune autre partie à venir.</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -243,8 +243,8 @@
|
|||||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
<h2 class="text-4xl font-bold text-neutral-900 mb-6">Prêt à vivre la nuit parisienne ?</h2>
|
<h2 class="text-4xl font-bold text-neutral-900 mb-6">Prêt à vivre la nuit parisienne ?</h2>
|
||||||
<p class="text-xl text-neutral-700 mb-8">Rejoignez des milliers de party-goers qui utilisent Aperonight chaque semaine</p>
|
<p class="text-xl text-neutral-700 mb-8">Rejoignez des milliers de party-goers qui utilisent Aperonight chaque semaine</p>
|
||||||
<button 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 text-lg transition-all duration-300 transform hover:scale-105 shadow-xl">
|
<%= link_to new_user_registration_path, 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 text-lg transition-all duration-300 transform hover:scale-105 shadow-xl" do %>
|
||||||
S'inscrire gratuitement
|
S'inscrire gratuitement
|
||||||
</button>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
25
app/views/shared/_flash_messages.html.erb
Normal file
25
app/views/shared/_flash_messages.html.erb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<% flash.each do |type, message| %>
|
||||||
|
<% if message.present? %>
|
||||||
|
<div class="rounded-md bg-green-50 border-green-100 p-4 border <%= flash_class(type) %> animate-fade-in" data-controller="flash-message">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="shrink-0">
|
||||||
|
<%= flash_icon(type) %>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 w-full">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-sm text-green-700">
|
||||||
|
<p class="text-sm font-medium"><%= message %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4 flex-shrink-0 flex">
|
||||||
|
<button data-action="click->flash-message#close" class="inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
||||||
|
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
225
auth-messages-implementation-plan.md
Normal file
225
auth-messages-implementation-plan.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# Authentication Messages Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document outlines the implementation of error/warn/info messages for login, registration, logout, password reset, and other authentication flows based on the existing purple/pink theme.
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
- **Theme**: Purple/pink gradient system with neutral colors
|
||||||
|
- **Authentication**: Devise with custom controllers
|
||||||
|
- **Missing**: Flash message display system
|
||||||
|
- **Existing**: Only form validation errors are displayed
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### 1. Flash Message Component
|
||||||
|
Create a reusable flash message component that integrates with the theme.
|
||||||
|
|
||||||
|
### 2. CSS Classes for Message Types
|
||||||
|
Add theme-consistent styles for different message types:
|
||||||
|
- Success (green/purple)
|
||||||
|
- Error (red)
|
||||||
|
- Warning (yellow/orange)
|
||||||
|
- Info (blue)
|
||||||
|
|
||||||
|
### 3. JavaScript Enhancement
|
||||||
|
Add auto-dismiss functionality and animations
|
||||||
|
|
||||||
|
### 4. Integration
|
||||||
|
Update layouts and views to use the new message system
|
||||||
|
|
||||||
|
## Files to Create/Update
|
||||||
|
|
||||||
|
### A. Flash Message Partial
|
||||||
|
**File**: `app/views/shared/_flash_messages.html.erb`
|
||||||
|
```erb
|
||||||
|
<% flash.each do |type, message| %>
|
||||||
|
<% if message.present? %>
|
||||||
|
<div class="flash-message <%= flash_class(type) %> animate-fade-in" data-controller="flash-message">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<%= flash_icon(type) %>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 flex-1">
|
||||||
|
<p class="text-sm font-medium"><%= message %></p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4 flex-shrink-0 flex">
|
||||||
|
<button data-action="click->flash-message#close" class="inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
||||||
|
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. Flash Message Styling
|
||||||
|
**File**: `app/assets/stylesheets/components/flash.css`
|
||||||
|
```css
|
||||||
|
/* Flash Messages - Theme Integration */
|
||||||
|
.flash-message {
|
||||||
|
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message .flash-container {
|
||||||
|
@apply rounded-lg p-4 shadow-md border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-success .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-green-50 to-purple-50 border-green-200 text-green-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-error .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-red-50 to-pink-50 border-red-200 text-red-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-warning .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-yellow-50 to-orange-50 border-yellow-200 text-yellow-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-info .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-blue-50 to-purple-50 border-blue-200 text-blue-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-notice .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-purple-50 to-pink-50 border-purple-200 text-purple-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-message-alert .flash-container {
|
||||||
|
@apply bg-gradient-to-r from-red-50 to-pink-50 border-red-200 text-red-800;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### C. Helper Methods
|
||||||
|
**File**: `app/helpers/flash_messages_helper.rb`
|
||||||
|
```ruby
|
||||||
|
module FlashMessagesHelper
|
||||||
|
def flash_class(type)
|
||||||
|
case type.to_s
|
||||||
|
when 'notice' then 'flash-message-success'
|
||||||
|
when 'success' then 'flash-message-success'
|
||||||
|
when 'error' then 'flash-message-error'
|
||||||
|
when 'alert' then 'flash-message-error'
|
||||||
|
when 'warning' then 'flash-message-warning'
|
||||||
|
when 'info' then 'flash-message-info'
|
||||||
|
else "flash-message-#{type}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def flash_icon(type)
|
||||||
|
case type.to_s
|
||||||
|
when 'notice', 'success'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-green-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
when 'error', 'alert'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-red-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
when 'warning'
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
content_tag :svg, class: "h-5 w-5 text-blue-400", fill: "currentColor", viewBox: "0 0 20 20" do
|
||||||
|
content_tag :path, "", "fill-rule": "evenodd", "d": "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", "clip-rule": "evenodd"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### D. JavaScript Controller
|
||||||
|
**File**: `app/javascript/controllers/flash_message_controller.js`
|
||||||
|
```javascript
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["message"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Auto-dismiss after 5 seconds
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.close()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.element.classList.add('opacity-0', 'transition-opacity', 'duration-300')
|
||||||
|
setTimeout(() => {
|
||||||
|
this.element.remove()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### E. Update Application Layout
|
||||||
|
**File**: `app/views/layouts/application.html.erb` (add flash messages)
|
||||||
|
```erb
|
||||||
|
<body class="h-full font-sans text-neutral-900 antialiased">
|
||||||
|
<div class="min-h-full">
|
||||||
|
<%= render "components/header" %>
|
||||||
|
|
||||||
|
<main class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<%= render "shared/flash_messages" %>
|
||||||
|
<%= yield %>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<%= render "components/footer" %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
### F. Update Authentication Views
|
||||||
|
Update all Devise views to remove the old error display and rely on flash messages.
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Authentication Flows to Test:
|
||||||
|
1. **Registration**
|
||||||
|
- Successful registration
|
||||||
|
- Registration with validation errors
|
||||||
|
- Email confirmation
|
||||||
|
|
||||||
|
2. **Login**
|
||||||
|
- Successful login
|
||||||
|
- Invalid credentials
|
||||||
|
- Account locked/unconfirmed
|
||||||
|
|
||||||
|
3. **Password Reset**
|
||||||
|
- Request reset email
|
||||||
|
- Reset password success/failure
|
||||||
|
|
||||||
|
4. **Account Management**
|
||||||
|
- Update profile
|
||||||
|
- Change password
|
||||||
|
- Delete account
|
||||||
|
|
||||||
|
### Message Types to Verify:
|
||||||
|
- [ ] Success messages (green/purple)
|
||||||
|
- [ ] Error messages (red/pink)
|
||||||
|
- [ ] Warning messages (yellow/orange)
|
||||||
|
- [ ] Info messages (blue/purple)
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
1. Create CSS classes and theme integration
|
||||||
|
2. Create helper methods
|
||||||
|
3. Create partial templates
|
||||||
|
4. Add to application layout
|
||||||
|
5. Test each authentication flow
|
||||||
|
6. Add JavaScript enhancements
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- All messages use the existing purple/pink theme colors
|
||||||
|
- Responsive design for mobile/desktop
|
||||||
|
- Auto-dismiss functionality with manual close option
|
||||||
|
- Smooth animations and transitions
|
||||||
|
- Accessibility compliant with focus indicators
|
||||||
@@ -23,5 +23,9 @@ module Aperonight
|
|||||||
#
|
#
|
||||||
# config.time_zone = "Central Time (US & Canada)"
|
# config.time_zone = "Central Time (US & Canada)"
|
||||||
# config.eager_load_paths << Rails.root.join("extras")
|
# config.eager_load_paths << Rails.root.join("extras")
|
||||||
|
|
||||||
|
config.i18n.load_path += Dir[Rails.root.join("my", "locales", "*.{rb,yml}")]
|
||||||
|
# config.i18n.default_locale = :fr
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ Rails.application.configure do
|
|||||||
# Don't care if the mailer can't send.
|
# Don't care if the mailer can't send.
|
||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
|
# Configure mailer to use localhost:1025 for development
|
||||||
|
config.action_mailer.delivery_method = :smtp
|
||||||
|
config.action_mailer.smtp_settings = {
|
||||||
|
address: "localhost",
|
||||||
|
port: 1025
|
||||||
|
}
|
||||||
|
|
||||||
# Make template changes take effect immediately.
|
# Make template changes take effect immediately.
|
||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
|||||||
@@ -60,14 +60,17 @@ Rails.application.configure do
|
|||||||
# Set host to be used by links generated in mailer templates.
|
# Set host to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
config.action_mailer.default_url_options = { host: "example.com" }
|
||||||
|
|
||||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
|
# Configure SMTP settings using environment variables
|
||||||
# config.action_mailer.smtp_settings = {
|
config.action_mailer.delivery_method = :smtp
|
||||||
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
config.action_mailer.smtp_settings = {
|
||||||
# password: Rails.application.credentials.dig(:smtp, :password),
|
address: ENV.fetch("SMTP_ADDRESS", "smtp.example.com"),
|
||||||
# address: "smtp.example.com",
|
port: ENV.fetch("SMTP_PORT", 587),
|
||||||
# port: 587,
|
user_name: ENV.fetch("SMTP_USERNAME", ""),
|
||||||
# authentication: :plain
|
password: ENV.fetch("SMTP_PASSWORD", ""),
|
||||||
# }
|
authentication: ENV.fetch("SMTP_AUTHENTICATION", "plain"),
|
||||||
|
domain: ENV.fetch("SMTP_DOMAIN", "example.com"),
|
||||||
|
enable_starttls_auto: ENV.fetch("SMTP_STARTTLS", true)
|
||||||
|
}
|
||||||
|
|
||||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||||
# the I18n.default_locale when a translation cannot be found).
|
# the I18n.default_locale when a translation cannot be found).
|
||||||
|
|||||||
46
config/locales/fr.yml
Normal file
46
config/locales/fr.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
fr:
|
||||||
|
views:
|
||||||
|
pagination:
|
||||||
|
first: "« Premier"
|
||||||
|
last: "Dernier »"
|
||||||
|
previous: "‹ Précédent"
|
||||||
|
next: "Suivant ›"
|
||||||
|
truncate: "…"
|
||||||
|
helpers:
|
||||||
|
page_entries_info:
|
||||||
|
one_page:
|
||||||
|
display_entries:
|
||||||
|
zero: "Aucun %{entry_name} trouvé"
|
||||||
|
one: "Affichage de <b>1</b> %{entry_name}"
|
||||||
|
other: "Affichage de <b>tous les %{count}</b> %{entry_name}"
|
||||||
|
more_pages:
|
||||||
|
display_entries: "Affichage de %{entry_name} <b>%{first} - %{last}</b> sur <b>%{total}</b> au total"
|
||||||
|
activerecord:
|
||||||
|
models:
|
||||||
|
user: "Utilisateur"
|
||||||
|
party: "Soirée"
|
||||||
|
ticket: "Billet"
|
||||||
|
ticket_type: "Type de billet"
|
||||||
|
attributes:
|
||||||
|
user:
|
||||||
|
email: "Email"
|
||||||
|
password: "Mot de passe"
|
||||||
|
password_confirmation: "Confirmation du mot de passe"
|
||||||
|
remember_me: "Se souvenir de moi"
|
||||||
|
party:
|
||||||
|
name: "Nom"
|
||||||
|
description: "Description"
|
||||||
|
start_date: "Date de début"
|
||||||
|
end_date: "Date de fin"
|
||||||
|
location: "Lieu"
|
||||||
|
capacity: "Capacité"
|
||||||
|
ticket:
|
||||||
|
user: "Utilisateur"
|
||||||
|
ticket_type: "Type de billet"
|
||||||
|
quantity: "Quantité"
|
||||||
|
price: "Prix"
|
||||||
|
ticket_type:
|
||||||
|
name: "Nom"
|
||||||
|
description: "Description"
|
||||||
|
price: "Prix"
|
||||||
|
available_quantity: "Quantité disponible"
|
||||||
@@ -12,21 +12,24 @@ Rails.application.routes.draw do
|
|||||||
# Defines the root path route ("/")
|
# Defines the root path route ("/")
|
||||||
root "pages#home"
|
root "pages#home"
|
||||||
|
|
||||||
# parties page
|
# Pages
|
||||||
get "parties", to: "parties#index", as: "parties"
|
get "dashboard", to: "pages#dashboard", as: "dashboard"
|
||||||
get "parties/:slug.:id", to: "parties#show", as: "party"
|
|
||||||
|
# Parties
|
||||||
|
get "parties", to: "parties#index", as: "parties"
|
||||||
|
get "parties/:slug.:id", to: "parties#show", as: "party"
|
||||||
|
|
||||||
# Routes for devise authentication Gem
|
# Routes for devise authentication Gem
|
||||||
# Bind devise to user
|
# Bind devise to user
|
||||||
# devise_for :users
|
# devise_for :users
|
||||||
devise_for :users, path: "auth", path_names: {
|
devise_for :users, path: "auth", path_names: {
|
||||||
sign_up: "register", # Route for user registration
|
sign_in: "sign_in", # Route for user login
|
||||||
sign_in: "login", # Route for user login
|
sign_out: "sign_out", # Route for user logout
|
||||||
sign_out: "logout", # Route for user logout
|
|
||||||
password: "reset-password", # Route for changing password
|
password: "reset-password", # Route for changing password
|
||||||
confirmation: "verification", # Route for account confirmation
|
confirmation: "verification", # Route for account confirmation
|
||||||
unlock: "unblock", # Route for account unlock
|
unlock: "unblock", # Route for account unlock
|
||||||
registration: "register" # Route for user registration (redundant with sign_up)
|
# registration: "account", # Route for user account
|
||||||
|
sign_up: "signup" # Route for user registration
|
||||||
},
|
},
|
||||||
controllers: {
|
controllers: {
|
||||||
sessions: "authentications/sessions", # Custom controller for sessions
|
sessions: "authentications/sessions", # Custom controller for sessions
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class DeviseCreateUsers < ActiveRecord::Migration[8.0]
|
|||||||
# t.string :unlock_token # Only if unlock strategy is :email or :both
|
# t.string :unlock_token # Only if unlock strategy is :email or :both
|
||||||
# t.datetime :locked_at
|
# t.datetime :locked_at
|
||||||
|
|
||||||
|
# Personnal informations
|
||||||
|
t.string :last_name, null: true # Nom
|
||||||
|
t.string :first_name, null: true # Prénom
|
||||||
|
|
||||||
t.timestamps null: false
|
t.timestamps null: false
|
||||||
end
|
end
|
||||||
|
|||||||
2
db/schema.rb
generated
2
db/schema.rb
generated
@@ -69,6 +69,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_08_23_171354) do
|
|||||||
t.string "reset_password_token"
|
t.string "reset_password_token"
|
||||||
t.datetime "reset_password_sent_at"
|
t.datetime "reset_password_sent_at"
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
|
t.string "last_name"
|
||||||
|
t.string "first_name"
|
||||||
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.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
admin_user = User.find_or_create_by!(email: 'admin@example.com') do |u|
|
admin_user = User.find_or_create_by!(email: 'admin@example.com') do |u|
|
||||||
u.password = 'password'
|
u.password = 'password'
|
||||||
u.password_confirmation = 'password'
|
u.password_confirmation = 'password'
|
||||||
|
u.last_name = nil
|
||||||
|
u.first_name = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create regular users for development
|
# Create regular users for development
|
||||||
@@ -21,6 +23,8 @@ missing_users_count.times do |i|
|
|||||||
User.find_or_create_by!(email: "user#{i + 1}@example.com") do |u|
|
User.find_or_create_by!(email: "user#{i + 1}@example.com") do |u|
|
||||||
u.password = 'password'
|
u.password = 'password'
|
||||||
u.password_confirmation = 'password'
|
u.password_confirmation = 'password'
|
||||||
|
u.last_name = nil
|
||||||
|
u.first_name = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -40,7 +44,7 @@ parties_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: "https://images.unsplash.com/photo-1506157786151-b84b9d3d78d8?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
|
image: "https://fastly.picsum.photos/id/407/300/200.jpg?hmac=9EhoXMZ1QdwJue90vzxcjBg2YzsZsAWCjJ7oxOhtcU0",
|
||||||
user: users.first
|
user: users.first
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
19
test.sh
Normal file
19
test.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Check if a directory/file argument is provided
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
# Get the test directory/file from the first argument
|
||||||
|
TEST_PATH="$1"
|
||||||
|
|
||||||
|
# Check if the provided argument is a directory or file
|
||||||
|
if [ -d "$TEST_PATH" ] || [ -f "$TEST_PATH" ]; then
|
||||||
|
# Run Rails tests in the specified directory/file
|
||||||
|
bundle exec rails test "$TEST_PATH"
|
||||||
|
else
|
||||||
|
echo "Error: $TEST_PATH is not a valid directory or file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Run Rails tests in the current directory
|
||||||
|
bundle exec rails test
|
||||||
|
fi
|
||||||
4
test/fixtures/users.yml
vendored
4
test/fixtures/users.yml
vendored
@@ -3,7 +3,11 @@
|
|||||||
one:
|
one:
|
||||||
email: user1@example.com
|
email: user1@example.com
|
||||||
encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
|
encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
|
||||||
|
last_name: Trump
|
||||||
|
first_name: Donald
|
||||||
|
|
||||||
two:
|
two:
|
||||||
email: user2@example.com
|
email: user2@example.com
|
||||||
encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
|
encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
|
||||||
|
last_name: Obama
|
||||||
|
first_name: Barack
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class TicketTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -109,6 +110,7 @@ class TicketTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -147,6 +149,7 @@ class TicketTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -184,6 +187,7 @@ class TicketTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -221,6 +225,7 @@ class TicketTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "should not save ticket_type without sale_start_at" do
|
test "should not save ticket_type without sale_start_at" do
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -60,7 +60,7 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "should not save ticket_type without sale_end_at" do
|
test "should not save ticket_type without sale_end_at" do
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -71,7 +71,7 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "should not save ticket_type with sale_end_at before sale_start_at" do
|
test "should not save ticket_type with sale_end_at before sale_start_at" do
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -87,9 +87,10 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
password: "password123",
|
password: "password123",
|
||||||
password_confirmation: "password123"
|
password_confirmation: "password123"
|
||||||
)
|
)
|
||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -97,9 +98,9 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
venue_address: "123 Test Street",
|
venue_address: "123 Test Street",
|
||||||
user: user
|
user: user
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -129,9 +130,10 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
password: "password123",
|
password: "password123",
|
||||||
password_confirmation: "password123"
|
password_confirmation: "password123"
|
||||||
)
|
)
|
||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -139,9 +141,9 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
venue_address: "123 Test Street",
|
venue_address: "123 Test Street",
|
||||||
user: user
|
user: user
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -159,9 +161,10 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
password: "password123",
|
password: "password123",
|
||||||
password_confirmation: "password123"
|
password_confirmation: "password123"
|
||||||
)
|
)
|
||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -169,9 +172,9 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
venue_address: "123 Test Street",
|
venue_address: "123 Test Street",
|
||||||
user: user
|
user: user
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -190,9 +193,10 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
password: "password123",
|
password: "password123",
|
||||||
password_confirmation: "password123"
|
password_confirmation: "password123"
|
||||||
)
|
)
|
||||||
|
|
||||||
party = Party.create!(
|
party = Party.create!(
|
||||||
name: "Valid Party Name",
|
name: "Valid Party Name",
|
||||||
|
slug: "valid-party-name",
|
||||||
description: "Valid description for the party that is long enough",
|
description: "Valid description for the party that is long enough",
|
||||||
latitude: 48.8566,
|
latitude: 48.8566,
|
||||||
longitude: 2.3522,
|
longitude: 2.3522,
|
||||||
@@ -200,9 +204,9 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
venue_address: "123 Test Street",
|
venue_address: "123 Test Street",
|
||||||
user: user
|
user: user
|
||||||
)
|
)
|
||||||
|
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -217,7 +221,7 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "should not save ticket_type with invalid minimum_age" do
|
test "should not save ticket_type with invalid minimum_age" do
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -230,7 +234,7 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "should not save ticket_type with minimum_age greater than 120" do
|
test "should not save ticket_type with minimum_age greater than 120" do
|
||||||
ticket_type = TicketType.new(
|
ticket_type = TicketType.new(
|
||||||
name: "Valid Ticket Type Name",
|
name: "Valid Ticket Type Name",
|
||||||
description: "Valid description for the ticket type that is long enough",
|
description: "Valid description for the ticket type that is long enough",
|
||||||
price_cents: 1000,
|
price_cents: 1000,
|
||||||
quantity: 50,
|
quantity: 50,
|
||||||
@@ -240,4 +244,4 @@ class TicketTypeTest < ActiveSupport::TestCase
|
|||||||
)
|
)
|
||||||
assert_not ticket_type.save
|
assert_not ticket_type.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,4 +25,42 @@ class UserTest < ActiveSupport::TestCase
|
|||||||
assert_equal :has_many, association.macro
|
assert_equal :has_many, association.macro
|
||||||
assert_equal :destroy, association.options[:dependent]
|
assert_equal :destroy, association.options[:dependent]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test first_name validations
|
||||||
|
test "should validate presence of first_name" do
|
||||||
|
user = User.new(last_name: "Doe")
|
||||||
|
refute user.valid?, "User with blank first_name should be invalid"
|
||||||
|
assert_not_nil user.errors[:first_name], "No validation error for blank first_name"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should validate length of first_name" do
|
||||||
|
# Test minimum length
|
||||||
|
user = User.new(first_name: "A", last_name: "Doe")
|
||||||
|
refute user.valid?, "User with first_name shorter than 3 chars should be invalid"
|
||||||
|
assert_not_nil user.errors[:first_name], "No validation error for too short first_name"
|
||||||
|
|
||||||
|
# Test maximum length
|
||||||
|
user = User.new(first_name: "A" * 13, last_name: "Doe")
|
||||||
|
refute user.valid?, "User with first_name longer than 12 chars should be invalid"
|
||||||
|
assert_not_nil user.errors[:first_name], "No validation error for too long first_name"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test last_name validations
|
||||||
|
test "should validate presence of last_name" do
|
||||||
|
user = User.new(first_name: "John")
|
||||||
|
refute user.valid?, "User with blank last_name should be invalid"
|
||||||
|
assert_not_nil user.errors[:last_name], "No validation error for blank last_name"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should validate length of last_name" do
|
||||||
|
# Test minimum length
|
||||||
|
user = User.new(first_name: "John", last_name: "Do")
|
||||||
|
refute user.valid?, "User with last_name shorter than 3 chars should be invalid"
|
||||||
|
assert_not_nil user.errors[:last_name], "No validation error for too short last_name"
|
||||||
|
|
||||||
|
# Test maximum length
|
||||||
|
user = User.new(first_name: "John", last_name: "D" * 13)
|
||||||
|
refute user.valid?, "User with last_name longer than 12 chars should be invalid"
|
||||||
|
assert_not_nil user.errors[:last_name], "No validation error for too long last_name"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user