develop #3

Merged
kbe merged 227 commits from develop into main 2025-09-16 14:35:23 +00:00
26 changed files with 607 additions and 68 deletions
Showing only changes of commit c226adc36c - Show all commits

View File

@@ -29,6 +29,15 @@ SMTP_PORT=1025
SMTP_AUTHENTICATION=plain
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
STRIPE_API_KEY=1337

View File

@@ -1,3 +1,3 @@
web: env RUBY_DEBUG_OPEN=true bin/rails server
js: yarn build --watch
js: yarn build:dev --watch
css: yarn build:css --watch

View File

@@ -3,6 +3,9 @@
/* Import Tailwind using PostCSS */
@import "tailwindcss";
/* Import flash message styles */
@import "components/flash";
/** Default text color */
body {
color: #555555;

View 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;
}

View File

@@ -23,9 +23,11 @@ class Authentications::PasswordsController < Devise::PasswordsController
# protected
# def after_resetting_password_path_for(resource)
# super(resource)
# end
# Override to set a flash message on successful password reset
def after_resetting_password_path_for(resource)
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
# def after_sending_reset_password_instructions_path_for(resource_name)

View File

@@ -9,9 +9,10 @@ class Authentications::SessionsController < Devise::SessionsController
# end
# POST /resource/sign_in
# def create
# super
# end
def create
super
flash[:notice] = "Connexion réussie !" if resource.persisted?
end
# DELETE /resource/sign_out
# def destroy

View File

@@ -3,17 +3,26 @@
class PagesController < ApplicationController
# Skip authentication for public pages
# skip_before_action :authenticate_user!, only: [ :home ]
before_action :authenticate_user!, only: [ :dashboard ]
# Homepage showing featured parties
def home
# @parties = Party.published.featured.limit(3)
@parties = Party.where(state: :published).order(created_at: :desc)
puts @parties
if user_signed_in?
return redirect_to(dashboard_path)
end
end
# User dashboard showing personalized content
# Accessible only to authenticated users
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
# Events page showing all published parties with pagination

View File

@@ -1,5 +1,9 @@
module ApplicationHelper
# Convert prince from cents to float
def format_price(cents)
(cents.to_f / 100).round(2)
end
# Include flash message helpers
include FlashMessagesHelper
end

View 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

View 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)
}
}

View File

@@ -5,9 +5,12 @@
import { application } from "./application"
import LogoutController from "./logout_controller"
import ShadcnTestController from "./shadcn_test_controller"
import FlashMessage from "./flash_message_controller"
import CounterController from "./counter_controller"
import ShadcnTestController from "./shadcn_test_controller"
application.register("logout", LogoutController)
application.register("shadcn-test", ShadcnTestController)
application.register("counter", CounterController)
application.register("logout", LogoutController) // Allow logout using js
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

View File

@@ -1,7 +1,6 @@
<h2>Resend confirmation instructions</h2>
<%= 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">
<%= f.label :email %><br />

View File

@@ -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>
<%= link_to "/" do %>
@@ -13,7 +13,6 @@
</div>
<%= 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 %>
<div class="space-y-4">

View File

@@ -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>
<%= link_to "/" do %>
@@ -13,7 +13,6 @@
</div>
<%= 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>
<%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>

View File

@@ -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>
<%= link_to "/" do %>
@@ -16,7 +16,6 @@
</div>
<%= 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>

View File

@@ -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>
<%= link_to "/" do %>
@@ -16,7 +16,6 @@
</div>
<%= 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="field">

View File

@@ -1,14 +1,5 @@
<% 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">
<h2 class="text-lg font-medium text-red-800 mb-3">
<%= I18n.t("errors.messages.not_saved",
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>
<% flash.now[:error] = message %>
<% end %>
</ul>
</div>
<% end %>

View File

@@ -1,7 +1,6 @@
<h2>Resend unlock instructions</h2>
<%= 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">
<%= f.label :email %><br />

View File

@@ -7,28 +7,30 @@
<meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<%# 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) %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "theme", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
</head>
<body class="h-full font-sans text-neutral-900 antialiased">
<div class="min-h-full">
<div class="">
<%= render "components/header" %>
<main class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flash">
<%= render "shared/flash_messages" %>
</div>
<div class="yield">
<%= yield %>
</div>
</main>
<footer class="bg-neutral-100 text-neutral-600">
@@ -36,6 +38,7 @@
<%= render "components/footer" %>
</div>
</footer>
</div>
</body>
</html>

View 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>

View 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 %>

View 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

View File

@@ -34,6 +34,13 @@ Rails.application.configure do
# Don't care if the mailer can't send.
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.
config.action_mailer.perform_caching = false

View File

@@ -60,14 +60,17 @@ Rails.application.configure do
# Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "example.com" }
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
# config.action_mailer.smtp_settings = {
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
# password: Rails.application.credentials.dig(:smtp, :password),
# address: "smtp.example.com",
# port: 587,
# authentication: :plain
# }
# Configure SMTP settings using environment variables
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV.fetch("SMTP_ADDRESS", "smtp.example.com"),
port: ENV.fetch("SMTP_PORT", 587),
user_name: ENV.fetch("SMTP_USERNAME", ""),
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
# the I18n.default_locale when a translation cannot be found).

View File

@@ -12,7 +12,10 @@ Rails.application.routes.draw do
# Defines the root path route ("/")
root "pages#home"
# parties page
# Pages
get "dashboard", to: "pages#dashboard", as: "dashboard"
# Parties
get "parties", to: "parties#index", as: "parties"
get "parties/:slug.:id", to: "parties#show", as: "party"

View File

@@ -44,7 +44,7 @@ parties_data = [
start_time: 1.day.from_now,
end_time: 1.day.from_now + 6.hours,
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
},
{