19 Commits

Author SHA1 Message Date
kbe
b9b7ac4ba1 Add flash message support and localization updates 2025-08-26 19:08:23 +02:00
kbe
cc7d71ad69 add compose file? 2025-08-26 18:53:18 +02:00
Kevin BATAILLE
062820873f fix: update header and layout structure for flash messages 2025-08-26 18:34:03 +02:00
Kevin BATAILLE
c226adc36c feat: implement flash messages system with auto-dismiss notifications
- Add flash message helper and styles for consistent notifications
- Replace Devise error messages with flash-based notifications
- Add dashboard page with event statistics
- Configure SMTP settings for development and production
- Update authentication controllers to use flash messages
- Add JavaScript controller for auto-dismiss functionality
2025-08-26 18:29:56 +02:00
Kevin BATAILLE
0879b3c924 fix(test): Slug was missing in ticket tests 2025-08-26 17:24:20 +02:00
Kevin BATAILLE
884c6a8262 feat(auth): enhance user registration with names and improve UI
- Add first_name and last_name fields to User model with validations
- Configure Devise registrations controller to accept name parameters
- Update registration form with name fields and improved styling
- Replace Twitter Bootstrap pagination with custom Tailwind components
- Add French locale translations for pagination and models
- Update header styling with responsive design improvements
- Add EditorConfig for consistent code formatting
- Fix logout controller URL handling and improve JavaScript
- Update seed data and test fixtures with name attributes
- Add comprehensive model tests for name validations
- Add test.sh script for easier test execution

💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
2025-08-26 17:17:50 +02:00
Kevin BATAILLE
6b37c67b47 wip: Kaminari pagination 2025-08-26 02:57:17 +02:00
Kevin BATAILLE
a43c47a5db docs: Add QWEN.md file to customize Qwen Code interactions
Add a QWEN.md file to establish project context, preferences,
and behavior guidelines for Qwen Code interactions with the
aperonight Ruby on Rails project. This helps ensure consistent
and appropriate responses tailored to this specific project setup.

This change follows the convention of using a QWEN.md file to
customize AI assistant behavior in development environments.
2025-08-26 02:41:28 +02:00
Kevin BATAILLE
a0f008bbd4 fix: Add slug column to parties table and update seed data
- Added slug column to parties table via migration
- Updated seed data to properly assign slug values to Party records
- Fixed NoMethodError when assigning slug attribute in seed file

This resolves the ActiveModel::UnknownAttributeError that occurred
when trying to set the slug attribute on Party model instances.
2025-08-26 02:30:34 +02:00
kb6e
6385c39c10 new modes 2025-08-25 20:17:44 +02:00
kbe
6fbd24e36e feat: implement dynamic event display with party images and seed data
• Files changed: app/controllers/pages_controller.rb, app/models/party.rb, app/views/pages/home.html.erb, db/migrate/20250823145902_create_parties.rb, db/schema.rb, db/seeds.rb
• Nature of changes: Added image support to parties, updated homepage to dynamically display parties, enhanced seed data with parties and ticket types, schema updates for foreign keys
• Purpose: Enable dynamic event display on homepage with real data instead of static placeholders, add image support for parties, improve database relationships
• Impact: Homepage now shows real party data from database, parties can have images, database schema improved with proper foreign keys
• Commit message: feat: implement dynamic event display with party images and seed data
2025-08-25 03:41:15 +02:00
kbe
632055c44d chore: linting stuff 2025-08-25 00:43:03 +02:00
kbe
03717dc95b feat(test): Add comprehensive unit tests for all Rails models
- Create detailed unit tests for Party, TicketType, Ticket, User, and ApplicationRecord models
- Add fixture files for all models with valid test data
- Fix enum syntax in Party model for Rails 8 compatibility
- Add 60 total model tests covering validations, associations, and business logic
- Ensure all tests pass successfully

This provides full test coverage for the application's data models.
2025-08-25 00:40:07 +02:00
kbe
7f4aded5aa refactor: move index definitions to create migrations for tickets and ticket_types 2025-08-24 23:58:10 +02:00
kbe
74484597d9 feat: update party model with promoter and time tracking
- Add promoter_id reference to parties table
- Add start_time and end_time datetime fields
- Replace venue fields with single location string
- Update database schema and migration files
- Add port configuration to database.yml
- Update architecture documentation

This commit enhances the party model to support better event tracking and management, including promoter information
and precise timing.
2025-08-24 23:22:03 +02:00
kbe
a558f7fc9a docs: add comprehensive technical architecture documentation
- Replace placeholder content with detailed architecture documentation
- Document core models, controllers, and database schema
- Include implementation recommendations for authentication, payments, and security
- Add frontend considerations for mobile ticket scanning
- Define routes configuration for the application

This provides a solid technical foundation for the Aperonight platform development.

💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
2025-08-24 22:33:27 +02:00
kbe
1c7a62acde refactor: clean up controller before_action definitions and routes
- Standardize before_action syntax in controllers by removing extraneous spaces
- Comment out unused bundles routes in API v1 namespace
- Clean up whitespace in routes file

These changes improve code consistency and maintainability while preparing for future feature development.

💘 Generated with Crush
Co-Authored-By: Crush <crush@charm.land>
2025-08-24 22:31:42 +02:00
kbe
98efdb44ac Modify theme for light one 2025-08-23 22:04:31 +02:00
kbe
5454e23220 New light theme 2025-08-23 21:28:35 +02:00
96 changed files with 4009 additions and 1356 deletions

64
.editorconfig Normal file
View 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

View File

@@ -2,7 +2,7 @@
RAILS_ENV=development RAILS_ENV=development
SECRET_KEY_BASE=a3f5c6e7b8d9e0f1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7 SECRET_KEY_BASE=a3f5c6e7b8d9e0f1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7
DEVISE_SECRET_KEY=your_devise_secret_key_here DEVISE_SECRET_KEY=your_devise_secret_key_here
APP_NAME=Pafterwork APP_NAME=Aperonight
# Database Configuration for production and development # Database Configuration for production and development
DB_HOST=localhost DB_HOST=localhost
@@ -29,5 +29,19 @@ 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
# OpenAI login
OPENAI_API_KEY=f66dbb5f-9770-4f81-b2ea-eb7370bc9aa5
OPENAI_BASE_URL=https://api.scaleway.ai/v1
OPENAI_MODEL=devstral-small-2505

6
.gitignore vendored
View File

@@ -37,3 +37,9 @@
!/app/assets/builds/.keep !/app/assets/builds/.keep
/node_modules /node_modules
# Docker shared directory
/data
# Ignore generated reports
/test/reports

0
.kamal/hooks/docker-setup.sample Executable file → Normal file
View File

0
.kamal/hooks/post-app-boot.sample Executable file → Normal file
View File

0
.kamal/hooks/post-deploy.sample Executable file → Normal file
View File

0
.kamal/hooks/post-proxy-reboot.sample Executable file → Normal file
View File

0
.kamal/hooks/pre-app-boot.sample Executable file → Normal file
View File

0
.kamal/hooks/pre-build.sample Executable file → Normal file
View File

0
.kamal/hooks/pre-connect.sample Executable file → Normal file
View File

0
.kamal/hooks/pre-deploy.sample Executable file → Normal file
View File

0
.kamal/hooks/pre-proxy-reboot.sample Executable file → Normal file
View File

2
.tool-versions Normal file
View File

@@ -0,0 +1,2 @@
ruby 3.4.4
nodejs 24.4.1

12
Gemfile
View File

@@ -51,6 +51,12 @@ group :development, :test do
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false gem "rubocop-rails-omakase", require: false
# Add SQlite3 for local testing
gem "sqlite3", "~> 2.7"
# Improve Minitest output
gem "minitest-reporters", "~> 1.7"
end end
group :development do group :development do
@@ -65,3 +71,9 @@ group :test do
end end
gem "devise", "~> 4.9" gem "devise", "~> 4.9"
# Pagination gem
gem "kaminari", "~> 1.2"
gem "kaminari-tailwind", "~> 0.1.0"
# gem "net-pop", "~> 0.1.2"

View File

@@ -74,6 +74,7 @@ GEM
uri (>= 0.13.1) uri (>= 0.13.1)
addressable (2.8.7) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
ansi (1.5.0)
ast (2.4.3) ast (2.4.3)
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.20)
@@ -117,7 +118,7 @@ GEM
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.3.0) et-orbi (1.3.0)
tzinfo tzinfo
fugit (1.11.1) fugit (1.11.2)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.2.1)
@@ -146,6 +147,19 @@ GEM
sshkit (>= 1.23.0, < 2.0) sshkit (>= 1.23.0, < 2.0)
thor (~> 1.3) thor (~> 1.3)
zeitwerk (>= 2.6.18, < 3.0) zeitwerk (>= 2.6.18, < 3.0)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
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)
@@ -161,6 +175,11 @@ GEM
matrix (0.4.3) matrix (0.4.3)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.5) minitest (5.25.5)
minitest-reporters (1.7.1)
ansi
builder
minitest (>= 5.0)
ruby-progressbar
msgpack (1.8.0) msgpack (1.8.0)
mysql2 (0.5.6) mysql2 (0.5.6)
net-imap (0.5.9) net-imap (0.5.9)
@@ -261,7 +280,7 @@ GEM
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.4.1) rexml (3.4.1)
rubocop (1.79.2) rubocop (1.80.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@@ -290,7 +309,7 @@ GEM
rubocop-performance (>= 1.24) rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30) rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
rubyzip (3.0.1) rubyzip (3.0.2)
securerandom (0.4.1) securerandom (0.4.1)
selenium-webdriver (4.35.0) selenium-webdriver (4.35.0)
base64 (~> 0.2) base64 (~> 0.2)
@@ -314,6 +333,12 @@ GEM
fugit (~> 1.11.0) fugit (~> 1.11.0)
railties (>= 7.1) railties (>= 7.1)
thor (>= 1.3.1) thor (>= 1.3.1)
sqlite3 (2.7.3-aarch64-linux-gnu)
sqlite3 (2.7.3-aarch64-linux-musl)
sqlite3 (2.7.3-arm-linux-gnu)
sqlite3 (2.7.3-arm-linux-musl)
sqlite3 (2.7.3-x86_64-linux-gnu)
sqlite3 (2.7.3-x86_64-linux-musl)
sshkit (1.24.0) sshkit (1.24.0)
base64 base64
logger logger
@@ -361,7 +386,6 @@ PLATFORMS
aarch64-linux-musl aarch64-linux-musl
arm-linux-gnu arm-linux-gnu
arm-linux-musl arm-linux-musl
x86_64-linux
x86_64-linux-gnu x86_64-linux-gnu
x86_64-linux-musl x86_64-linux-musl
@@ -375,6 +399,9 @@ DEPENDENCIES
jbuilder jbuilder
jsbundling-rails jsbundling-rails
kamal kamal
kaminari (~> 1.2)
kaminari-tailwind (~> 0.1.0)
minitest-reporters (~> 1.7)
mysql2 (~> 0.5) mysql2 (~> 0.5)
propshaft propshaft
puma (>= 5.0) puma (>= 5.0)
@@ -384,6 +411,7 @@ DEPENDENCIES
solid_cable solid_cable
solid_cache solid_cache
solid_queue solid_queue
sqlite3 (~> 2.7)
stimulus-rails stimulus-rails
thruster thruster
turbo-rails turbo-rails

View File

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

25
QWEN.md Normal file
View File

@@ -0,0 +1,25 @@
# Qwen Code Customization
## Project Context
- Working on a Ruby on Rails project named "aperonight"
- Using Docker for containerization
- Following Ruby version 3.1.0 (as indicated by .ruby-version)
- Using Bundler for gem management (Gemfile)
- Using Node.js for frontend assets (package.json likely present)
## Preferences
- Prefer to use Ruby and Rails conventions
- Follow Docker best practices for development environments
- Use standard Ruby/Rails project structure
- When creating new files, follow Rails conventions
- When modifying existing files, maintain consistency with current code style
- Use git for version control (as seen in .gitignore)
- Prefer to work with the project's existing toolchain (Bundler, etc.)
## Behavior
- When asked to make changes, first understand the context by examining relevant files
- When creating new files, ensure they follow project conventions
- When modifying files, preserve existing code style and patterns
- When implementing new features, suggest appropriate file locations and naming conventions
- When debugging, suggest using the project's existing test suite and development tools
- When suggesting changes, provide clear explanations of why the change is beneficial

View File

@@ -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;

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

@@ -0,0 +1,161 @@
/* Theme Rules from docs/theme-rules.md - Light Theme Only */
/* Custom properties for the design system */
:root {
/* Primary - Purple gradient system */
--color-primary-50: #faf5ff;
--color-primary-100: #f3e8ff;
--color-primary-200: #e9d5ff;
--color-primary-300: #d8b4fe;
--color-primary-400: #c084fc;
--color-primary-500: #a855f7;
--color-primary-600: #9333ea;
--color-primary-700: #7e22ce;
--color-primary-800: #6b21a8;
--color-primary-900: #581c87;
/* Accent - Pink gradient */
--color-accent-400: #f472b6;
--color-accent-500: #ec4899;
--color-accent-600: #db2777;
/* Neutral - Slate system */
--color-neutral-50: #f8fafc;
--color-neutral-100: #f1f5f9;
--color-neutral-200: #e2e8f0;
--color-neutral-300: #cbd5e1;
--color-neutral-400: #94a3b8;
--color-neutral-500: #64748b;
--color-neutral-600: #475569;
--color-neutral-700: #334155;
--color-neutral-800: #1e293b;
--color-neutral-900: #0f172a;
/* Font families */
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* Spacing scale */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
}
/* Button components */
.btn-primary {
@apply bg-gradient-to-r from-purple-600 to-pink-600 text-white font-medium py-2 px-4 rounded-lg shadow-sm hover:shadow-md transition-all duration-200;
}
.btn-secondary {
@apply bg-white text-purple-600 border border-purple-200 font-medium py-2 px-4 rounded-lg hover:bg-purple-50 transition-colors duration-200;
}
.btn-destructive {
@apply bg-red-600 text-white font-medium py-2 px-4 rounded-lg shadow-sm hover:bg-red-700 transition-colors duration-200;
}
/* Card components */
.card {
@apply bg-white rounded-lg shadow-sm border border-neutral-200 p-6 hover:shadow-md transition-shadow duration-200;
}
.card-header {
@apply pb-4 border-b border-neutral-200 mb-4;
}
.card-body {
@apply space-y-4;
}
/* Form components */
.form-input {
@apply block w-full rounded-md border-neutral-300 shadow-sm focus:border-purple-500 focus:ring-purple-500 sm:text-sm;
}
.form-label {
@apply block text-sm font-medium text-neutral-700 mb-1;
}
.form-error {
@apply text-sm text-red-600 mt-1;
}
/* Navigation */
.nav-link {
@apply text-neutral-600 hover:text-purple-600 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200;
}
.nav-link-active {
@apply text-purple-600 bg-purple-50;
}
/* Layout utilities */
.container {
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
}
.grid-responsive {
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6;
}
.grid-cards {
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6;
}
/* Animation utilities */
.hover-lift {
@apply transition-transform duration-200 hover:-translate-y-1;
}
.hover-glow {
@apply transition-all duration-200 hover:shadow-lg hover:shadow-purple-500/25;
}
.focus-ring {
@apply focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2;
}
.transition-fast {
@apply transition-all duration-150 ease-in-out;
}
.transition-normal {
@apply transition-all duration-200 ease-in-out;
}
.transition-slow {
@apply transition-all duration-300 ease-in-out;
}
/* State utilities */
.disabled {
@apply opacity-50 cursor-not-allowed;
}
.animate-pulse-subtle {
@apply animate-pulse;
animation-duration: 3s;
}
.fade-in {
@apply animate-in fade-in-0 duration-500;
}
/* Accessibility utilities */
.focus-visible {
@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2;
}
.text-primary {
@apply text-neutral-900;
}
.text-secondary {
@apply text-neutral-600;
}

View File

@@ -4,7 +4,7 @@ module Api
module V1 module V1
class PartiesController < ApiController class PartiesController < ApiController
# Load party before specific actions to reduce duplication # Load party before specific actions to reduce duplication
before_action :set_party, only: [:show, :update, :destroy] before_action :set_party, only: [ :show, :update, :destroy ]
# GET /api/v1/parties # GET /api/v1/parties
# Returns all parties sorted by creation date (newest first) # Returns all parties sorted by creation date (newest first)

View File

@@ -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)

View File

@@ -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)

View File

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

View File

@@ -1,12 +1,32 @@
# Controller for static pages and user dashboard # Controller for static pages and user dashboard
# Handles basic page rendering and user-specific content # Handles basic page rendering and user-specific content
class PagesController < ApplicationController class PagesController < ApplicationController
# Require user authentication for dashboard access # Skip authentication for public pages
# Redirects to login page if user is not signed in # skip_before_action :authenticate_user!, only: [ :home ]
before_action :authenticate_user!, only: [:dashboard] 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)
if user_signed_in?
return redirect_to(dashboard_path)
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
# Events page showing all published parties with pagination
def events
@parties = Party.published.order(created_at: :desc).page(params[:page])
end end
end end

View File

@@ -0,0 +1,12 @@
class PartiesController < ApplicationController
# Display all events
def index
@parties = Party.includes(:user).upcoming.page(params[:page]).per(1)
# @parties = Party.page(params[:page]).per(12)
end
# Display desired event
def show
@party = Party.find(params[:id])
end
end

View File

@@ -1,2 +1,9 @@
module ApplicationHelper 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 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

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

View File

@@ -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, {

View File

@@ -6,23 +6,30 @@ class Party < ApplicationRecord
# published: Party is visible to public and can be discovered # published: Party is visible to public and can be discovered
# canceled: Party has been canceled by organizer # canceled: Party has been canceled by organizer
# sold_out: Party has reached capacity and tickets are no longer available # sold_out: Party has reached capacity and tickets are no longer available
enum state: { enum :state, {
draft: 0, draft: 0,
published: 1, published: 1,
canceled: 2, canceled: 2,
sold_out: 3 sold_out: 3
}, default: :draft }, default: :draft
# === Relations ===
belongs_to :user
has_many :ticket_types, dependent: :destroy
has_many :tickets, through: :ticket_types
# Validations for party attributes # Validations for party attributes
# Basic information # Basic information
validates :name, presence: true, length: { minimum: 3, maximum: 100 } validates :name, presence: true, length: { minimum: 3, maximum: 100 }
validates :slug, presence: true, length: { minimum: 3, maximum: 100 }
validates :description, presence: true, length: { minimum: 10, maximum: 1000 } validates :description, presence: true, length: { minimum: 10, maximum: 1000 }
validates :state, presence: true, inclusion: { in: states.keys } validates :state, presence: true, inclusion: { in: states.keys }
validates :image, length: { maximum: 500 } # URL or path to image
# Venue information # Venue information
validates :venue_name, presence: true, length: { maximum: 100 } validates :venue_name, presence: true, length: { maximum: 100 }
validates :venue_address, presence: true, length: { maximum: 200 } validates :venue_address, presence: true, length: { maximum: 200 }
# Geographic coordinates for map display # Geographic coordinates for map display
validates :latitude, presence: true, numericality: { validates :latitude, presence: true, numericality: {
greater_than_or_equal_to: -90, greater_than_or_equal_to: -90,
@@ -37,4 +44,8 @@ class Party < ApplicationRecord
scope :featured, -> { where(featured: true) } # Get featured parties for homepage scope :featured, -> { where(featured: true) } # Get featured parties for homepage
scope :published, -> { where(state: :published) } # Get publicly visible parties scope :published, -> { where(state: :published) } # Get publicly visible parties
scope :search_by_name, ->(query) { where("name ILIKE ?", "%#{query}%") } # Search by name (case-insensitive) scope :search_by_name, ->(query) { where("name ILIKE ?", "%#{query}%") } # Search by name (case-insensitive)
end
# Scope for published parties ordered by start time
scope :upcoming, -> { published.where("start_time >= ?", Time.current).order(start_time: :asc) }
end

View File

@@ -1,8 +1,22 @@
class Ticket < ApplicationRecord class Ticket < ApplicationRecord
# Associations
belongs_to :user
belongs_to :ticket_type
has_one :party, through: :ticket_type
# Validations # Validations
validates :qr_code, presence: true, uniqueness: true validates :qr_code, presence: true, uniqueness: true
validates :party_id, presence: true
validates :user_id, presence: true validates :user_id, presence: true
validates :ticket_type_id, presence: true
validates :price_cents, presence: true, numericality: { greater_than: 0 } validates :price_cents, presence: true, numericality: { greater_than: 0 }
validates :status, presence: true, inclusion: { in: %w[active used expired refunded] } validates :status, presence: true, inclusion: { in: %w[active used expired refunded] }
before_validation :set_price_from_ticket_type, on: :create
private
def set_price_from_ticket_type
return unless ticket_type
self.price_cents = ticket_type.price_cents
end
end end

View File

@@ -1,7 +1,7 @@
class TicketType < ApplicationRecord class TicketType < ApplicationRecord
# Associations # Associations
belongs_to :party belongs_to :party
has_many :tickets has_many :tickets, dependent: :destroy
# Validations # Validations
validates :name, presence: true, length: { minimum: 3, maximum: 50 } validates :name, presence: true, length: { minimum: 3, maximum: 50 }
@@ -12,7 +12,7 @@ class TicketType < ApplicationRecord
validates :sale_start_at, presence: true validates :sale_start_at, presence: true
validates :sale_end_at, presence: true validates :sale_end_at, presence: true
validate :sale_end_after_start validate :sale_end_after_start
validates :requires_id, inclusion: { in: [true, false] } validates :requires_id, inclusion: { in: [ true, false ] }
validates :minimum_age, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 120 }, allow_nil: true validates :minimum_age, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 120 }, allow_nil: true
private private

View File

@@ -18,4 +18,12 @@ class User < ApplicationRecord
# :omniauthable - allows authentication via OAuth providers # :omniauthable - allows authentication via OAuth providers
devise :database_authenticatable, :registerable, devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable :recoverable, :rememberable, :validatable
# Relationships
has_many :parties, 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

View File

@@ -1,23 +1,23 @@
<footer class="py-10 bg-gray-900 text-white border-t-1 border-solid border-color-gray"> <footer class="py-10 bg-neutral-100 text-neutral-600 border-t border-neutral-200">
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8"> <div class="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Column 1: About --> <!-- Column 1: About -->
<div class="space-y-4"> <div class="space-y-4">
<h3 class="text-lg font-semibold text-indigo-400">À propos</h3> <h3 class="text-lg font-semibold text-purple-500">À propos</h3>
<p class="text-sm text-gray-300 leading-relaxed"> <p class="text-sm text-neutral-600 leading-relaxed">
Aperonight est la plateforme qui connecte les amateurs de soirées aux meilleurs événements de leur ville. Aperonight est la plateforme qui connecte les amateurs de soirées aux meilleurs événements de leur ville.
</p> </p>
<div class="flex space-x-4"> <div class="flex space-x-4">
<a href="#" class="text-gray-400 hover:text-white transition-colors"> <a href="#" class="text-neutral-500 hover:text-purple-500 transition-colors">
<span class="sr-only">Facebook</span> <span class="sr-only">Facebook</span>
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 20.128 22 15.991 22 12z" clip-rule="evenodd"/>
</svg> </svg>
</a> </a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"> <a href="#" class="text-neutral-500 hover:text-purple-500 transition-colors">
<span class="sr-only">Instagram</span> <span class="sr-only">Instagram</span>
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.024.06 1.378.06 3.808s-.012 2.784-.06 3.808c-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.024.048-1.378.06-3.808.06s-2.784-.012-3.808-.06c-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.048-1.024-.06-1.378-.06-3.808s.012-2.784.06-3.808c.049-1.064.218-1.791.465-2.427A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802c-.468 0-.514.043-.514.043v2.378h2.575c0-2.378 0-2.378-.514-2.378zm-2.189 2.378v-2.378c0-.468.043-.514.043-.514h-2.378v2.892h2.335zm1.097 1.097c-.468 0-.514.043-.514.043v2.335h2.892v-2.892h-2.378c-.468 0-.514.043-.514.043v.514zm-1.097 1.097v-2.335c0-.468.043-.514.043-.514h-2.378v2.892h2.335z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.024.06 1.378.06 3.808s-.012 2.784-.06 3.808c-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.024.048-1.378.06-3.808.06s-2.784-.012-3.808-.06c-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.048-1.024-.06-1.378-.06-3.808s.012-2.784.06-3.808c.049-1.064.218-1.791.465-2.427A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63z" clip-rule="evenodd"/>
</svg> </svg>
</a> </a>
</div> </div>
@@ -25,59 +25,59 @@
<!-- Column 2: Quick Links --> <!-- Column 2: Quick Links -->
<div class="space-y-4"> <div class="space-y-4">
<h3 class="text-lg font-semibold text-indigo-400">Liens rapides</h3> <h3 class="text-lg font-semibold text-purple-500">Liens rapides</h3>
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li><%= link_to "Accueil", "/", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Accueil", "/", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "Événements", "/events", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Événements", "/events", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "Organisateurs", "/organizers", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Organisateurs", "/organizers", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "Support", "/support", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Support", "/support", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
</ul> </ul>
</div> </div>
<!-- Column 3: Legal --> <!-- Column 3: Legal -->
<div class="space-y-4"> <div class="space-y-4">
<h3 class="text-lg font-semibold text-indigo-400">Légal</h3> <h3 class="text-lg font-semibold text-purple-500">Légal</h3>
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li><%= link_to "Conditions d'utilisation", "/terms", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Conditions d'utilisation", "/terms", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "Politique de confidentialité", "/privacy", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Politique de confidentialité", "/privacy", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "CGV", "/cgv", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "CGV", "/cgv", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
<li><%= link_to "Mentions légales", "/legal", class: "text-gray-300 hover:text-white transition-colors" %></li> <li><%= link_to "Mentions légales", "/legal", class: "text-neutral-600 hover:text-purple-500 transition-colors" %></li>
</ul> </ul>
</div> </div>
<!-- Column 4: Contact --> <!-- Column 4: Contact -->
<div class="space-y-4"> <div class="space-y-4">
<h3 class="text-lg font-semibold text-indigo-400">Contact</h3> <h3 class="text-lg font-semibold text-purple-500">Contact</h3>
<address class="not-italic text-sm text-gray-300 space-y-2"> <address class="not-italic text-sm text-neutral-600 space-y-2">
<p> <p>
<span class="block font-medium">Email:</span> <span class="block font-medium">Email:</span>
<a href="mailto:hello@aperonight.com" class="text-indigo-400 hover:text-indigo-300 transition-colors"> <a href="mailto:hello@aperonight.com" class="text-purple-500 hover:text-purple-400 transition-colors">
hello@aperonight.com hello@aperonight.com
</a> </a>
</p> </p>
<p> <p>
<span class="block font-medium">Support:</span> <span class="block font-medium">Support:</span>
<a href="mailto:support@aperonight.com" class="text-indigo-400 hover:text-indigo-300 transition-colors"> <a href="mailto:support@aperonight.com" class="text-purple-500 hover:text-purple-400 transition-colors">
support@aperonight.com support@aperonight.com
</a> </a>
</p> </p>
</address> </address>
<p class="text-xs text-gray-400"> <p class="text-xs text-neutral-500">
Réponse sous 24h en semaine Réponse sous 24h en semaine
</p> </p>
</div> </div>
</div> </div>
<!-- Bottom Bar --> <!-- Bottom Bar -->
<div class="mt-12 pt-8 border-t border-gray-800"> <div class="mt-12 pt-8 border-t border-neutral-200">
<div class="flex flex-col md:flex-row justify-between items-center"> <div class="flex flex-col md:flex-row justify-between items-center">
<p class="text-sm text-gray-400"> <p class="text-sm text-neutral-500">
© <%= Time.current.year %> Aperonight. Tous droits réservés. © <%= Time.current.year %> Aperonight. Tous droits réservés.
</p> </p>
<p class="text-xs text-gray-500 mt-2 md:mt-0"> <p class="text-xs text-neutral-400 mt-2 md:mt-0">
Fait avec 💜 pour la communauté Fait avec 💜 pour la communauté
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -1,137 +1,141 @@
<nav x-data="{ open: false }" class="bg-black border-b border-gray-100"> <header class="shadow-sm border-b border-neutral-200">
<!-- Primary Navigation Menu --> <div class="bg-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <nav x-data="{ open: false }" class="bg-blue border-b border-purple-700">
<div class="flex justify-between h-16"> <!-- Primary Navigation Menu -->
<div class="flex"> <div class="container mx-auto px-4 sm:px-6 lg:px-8">
<!-- Logo --> <div class="flex justify-between h-16">
<div class="shrink-0 flex items-center"> <div class="flex">
<%= link_to Rails.application.config.app_name, "/", class: "text-white text-xl font-bold" %> <!-- Logo -->
</div> <div class="shrink-0 flex items-center">
<%= link_to Rails.application.config.app_name, current_user ? "/dashboard" : "/", class: "text-xl font-bold text-white" %>
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<!-- Parties -->
<%= link_to "Soirées et afterworks", "#",
class: "inline-flex
items-center px-1 pt-1 border-b-2 border-transparent
text-sm font-medium leading-5 text-gray-300
hover:text-white hover:border-white focus:outline-none
focus:text-white focus:border-white transition
duration-150 ease-in-out" %>
<!-- /Parties -->
<!-- Concerts -->
<%= link_to "Concerts", "#",
class: "inline-flex
items-center px-1 pt-1 border-b-2 border-transparent
text-sm font-medium leading-5 text-gray-300
hover:text-white hover:border-white focus:outline-none
focus:text-white focus:border-white transition
duration-150 ease-in-out" %>
<!-- /Parties -->
</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="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
<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>
<!-- Navigation Links -->
<div x-show="open" <div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex items-center">
x-transition:enter="transition ease-out duration-200" <%= link_to t('header.parties'), parties_path,
x-transition:enter-start="opacity-0 scale-95" class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
x-transition:enter-end="opacity-100 scale-100" %>
x-transition:leave="transition ease-in duration-75" <%= link_to t('header.concerts'), "#" ,
x-transition:leave-start="opacity-100 scale-100" class: "text-white hover:text-purple-200 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
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-black ring-opacity-5 py-1 bg-white">
<%= link_to "Mon profil", edit_user_registration_path, class: "block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out" %>
<%= link_to "Mes réservations", "#", class: "block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out" %>
<!-- Logout -->
<%= 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: "inline-block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out" %>
</div>
</div> </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 t('header.profile') , 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 t('header.reservations') , "#" ,
class: "block w-full px-4 py-2 text-start text-sm leading-5 text-white hover:bg-purple-700" %>
<%= link_to t('header.logout') , 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 t('header.login') , 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 t('header.register') , 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>
<% else %> </div>
<!-- Login/Register Links --> <!-- Responsive Navigation Menu -->
<div class="hidden sm:flex sm:items-center sm:ms-6 space-x-4"> <div :class="{ 'block': open, 'hidden': !open }" class="hidden sm:hidden">
<%= link_to "S'inscrire", new_user_registration_path, class: "inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-300 hover:text-white focus:outline-none transition ease-in-out duration-150" %> <div class="pt-2 pb-3 space-y-1 bg-purple-600">
<%= link_to "Se connecter", new_user_session_path, class: "inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-300 hover:text-white focus:outline-none transition ease-in-out duration-150" %> <%= link_to t('header.parties') , "#" ,
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
%>
<%= link_to t('header.concerts') , "#" ,
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
%>
</div> </div>
<% end %> <!-- 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 t('header.profile') , 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 t('header.reservations') , "#" ,
class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
%>
<!-- Hamburger --> <%= link_to t('header.logout') , destroy_user_session_path, data: { controller: "logout" , action: "click->logout#signOut",
<div class="-me-2 flex items-center sm:hidden"> logout_url_value: destroy_user_session_path, login_url_value: new_user_session_path, turbo: false },
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"> class: "block px-3 py-2 rounded-md text-base font-medium text-white hover:text-purple-200 hover:bg-purple-700"
<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" /> </div>
<path :class="{ 'hidden': !open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <% else %>
</svg> <div class="mt-3 space-y-1">
</button> <%= link_to t('header.register') , 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 t('header.login') , 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> </div>
</div> </nav>
</div>
<!-- Responsive Navigation Menu --> </header>
<div :class="{ 'block': open, 'hidden': !open }" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<%= link_to "Soirées et afterworks", "#", class: "block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
<%= link_to "Concerts", "#", class: "block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
</div>
<!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200">
<% if user_signed_in? %>
<div class="px-4">
<div class="font-medium text-base text-gray-800"><%= current_user.email %></div>
<div class="font-medium text-sm text-gray-500"><%= current_user.email %></div>
</div>
<div class="mt-3 space-y-1">
<%= link_to "Mon profil", edit_user_registration_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
<%= link_to "Mes réservations", "#", class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
<!-- Logout -->
<%= link_to "Déconnexion", destroy_user_session_path,
data: {
controller: "logout",
action: "click->logout#signOut",
logout_url_value: destroy_user_session_path,
login_url_value: new_user_session_path,
turbo: false
},
class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
</div>
<% else %>
<div class="mt-3 space-y-1">
<%= link_to "S'inscrire", new_user_registration_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
<%= link_to "Se connecter", new_user_session_path, class: "block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-400 hover:text-white hover:bg-gray-800 hover:border-white focus:outline-none focus:text-white focus:bg-gray-800 focus:border-white transition duration-150 ease-in-out" %>
</div>
<% end %>
</div>
</div>
</nav>

View File

@@ -1,16 +1,34 @@
<h2>Resend confirmation instructions</h2> <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 %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
<%= t('devise.confirmations.new.title') %>
</h2>
<p class="mt-2 text-center text-sm text-neutral-600">
<%= t('devise.confirmations.new.description') %>
</p>
</div>
<%= 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, class: "mt-8 space-y-6" }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field"> <div>
<%= f.label :email %><br /> <%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> <div class="mt-1">
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.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",
placeholder: "Email" %>
</div>
</div>
<div>
<%= f.submit t('devise.confirmations.new.submit'),
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>
<% end %>
<%= render "devise/shared/links" %>
</div> </div>
</div>
<div class="actions">
<%= f.submit "Resend confirmation instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,25 +1,43 @@
<h2>Change your password</h2> <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 %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
<%= t('devise.passwords.edit.title') %>
</h2>
<p class="mt-2 text-center text-sm text-neutral-600">
<%= t('devise.passwords.edit.description') %>
</p>
</div>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) 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="field"> <div class="space-y-4">
<%= f.label :password, "New password" %><br /> <div>
<% if @minimum_password_length %> <%= f.label :password, t('devise.passwords.edit.new_password'), class: "block text-sm font-medium text-neutral-700" %>
<em>(<%= @minimum_password_length %> characters minimum)</em><br /> <% if @minimum_password_length %>
<em class="text-sm text-neutral-500">(<%= t('devise.registrations.new.minimum_password_length', count: @minimum_password_length) %>)</em>
<% end %>
<%= 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" %>
</div>
<div>
<%= f.label :password_confirmation, t('devise.passwords.edit.confirm_new_password'), class: "block text-sm font-medium text-neutral-700" %>
<%= 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" %>
</div>
</div>
<div class="actions">
<%= f.submit t('devise.passwords.edit.submit'),
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>
<% end %> <% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
</div>
<div class="field"> <%= render "devise/shared/links" %>
<%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div> </div>
</div>
<div class="actions">
<%= f.submit "Change my password" %>
</div>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,16 +1,46 @@
<h2>Forgot your password?</h2> <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 %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
<%= t('devise.passwords.new.title') %>
</h2>
<p class="mt-2 text-center text-sm text-neutral-600">
<%= t('devise.passwords.new.description') %>
</p>
</div>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) 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 class="field"> <div>
<%= f.label :email %><br /> <%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
<%= f.email_field :email, autofocus: true, autocomplete: "email" %> <div class="mt-1">
<%= 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",
placeholder: t('devise.passwords.new.email_placeholder') %>
</div>
</div>
<div>
<%= f.submit t('devise.passwords.new.submit'),
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>
<% end %>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-neutral-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-neutral-50 text-neutral-600"> <%= t('devise.sessions.new.continue_with') %> </span>
</div>
</div>
<%= render "devise/shared/links" %>
</div>
</div> </div>
<div class="actions">
<%= f.submit "Send me reset password instructions" %>
</div> </div>
<% end %> </div>
<%= render "devise/shared/links" %>

View File

@@ -1,43 +1,70 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2> <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="max-w-2xl w-full space-y-8">
<div>
<%= link_to "/" do %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
Modifier votre compte
</h2>
<p class="mt-2 text-center text-sm text-neutral-600">
Gérez vos informations et préférences
</p>
</div>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "mt-8 space-y-6" }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %> <%= render "devise/shared/error_messages", resource: resource %>
<div class="field"> <div class="space-y-6">
<%= f.label :email %><br /> <div>
<%= f.email_field :email, autofocus: true, autocomplete: "email" %> <%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
</div> <%= 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-400 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm" %>
</div>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <div class="text-sm text-neutral-600">
<% end %> En attente de confirmation pour : <%= resource.unconfirmed_email %>
</div>
<% end %>
<div class="field"> <div>
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.label :password, "Nouveau mot de passe", class: "block text-sm font-medium text-neutral-700" %>
<%= f.password_field :password, autocomplete: "new-password" %> <i class="text-sm text-neutral-500">(laissez vide si vous ne souhaitez pas le changer)</i>
<% if @minimum_password_length %> <%= f.password_field :password, autocomplete: "new-password",
<br /> 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" %>
<em><%= @minimum_password_length %> characters minimum</em> </div>
<div>
<%= f.label :password_confirmation, t('devise.registrations.edit.confirm_new_password'), class: "block text-sm font-medium text-neutral-700" %>
<%= 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" %>
</div>
<div>
<%= f.label :current_password, t('devise.registrations.edit.current_password'), class: "block text-sm font-medium text-neutral-700" %>
<i class="text-sm text-neutral-500">(<%= t('devise.registrations.edit.current_password_required') %>)</i>
<%= f.password_field :current_password, autocomplete: "current-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" %>
</div>
</div>
<div class="flex items-center justify-between">
<%= f.submit t('devise.registrations.edit.update'),
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>
<% end %> <% end %>
<h3 class="text-center text-lg font-medium text-neutral-900"><%= t('devise.registrations.edit.delete_account') %></h3>
<div class="text-center">
<p class="text-sm text-neutral-600">
<%= t('devise.registrations.edit.unhappy') %> <%= button_to t('devise.registrations.edit.delete_account'), registration_path(resource_name),
data: { confirm: t('devise.registrations.edit.confirm_delete'), turbo_confirm: t('devise.registrations.edit.confirm_delete') },
method: :delete,
class: "font-medium text-red-600 hover:text-red-500" %>
</p>
</div>
<%= link_to t('devise.registrations.edit.back'), :back, class: "text-center block text-purple-600 hover:text-purple-500" %>
</div> </div>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<% end %>
<h3>Cancel my account</h3>
<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
<%= link_to "Back", :back %>

View File

@@ -1,29 +1,63 @@
<h2>Sign up</h2> <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 %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
<%= t('devise.registrations.new.title') %>
</h2>
<p class="mt-2 text-center text-sm text-neutral-600">
<%= t('devise.registrations.new.or') %>
<a href="<%= new_user_session_path %>" class="font-medium text-purple-600 hover:text-purple-500">
<%= t('devise.registrations.new.sign_in_link') %>
</a>
</p>
</div>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) 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="field"> <div class="space-y-4">
<%= f.label :email %><br /> <div>
<%= f.email_field :email, autofocus: true, autocomplete: "email" %> <%= f.label :email, class: "block text-sm font-medium text-neutral-700" %>
<%= 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" %>
</div>
<div>
<%= f.label :password, class: "block text-sm font-medium text-neutral-700" %>
<% if @minimum_password_length %>
<em class="text-sm text-neutral-500">(<%= t('devise.registrations.new.minimum_password_length', count: @minimum_password_length) %>)</em>
<% end %>
<%= 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" %>
</div>
<div>
<%= f.label :password_confirmation, class: "block text-sm font-medium text-neutral-700" %>
<%= 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" %>
</div>
</div>
<div class="actions">
<%= f.submit t('devise.registrations.new.sign_up'), 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>
<% end %>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-neutral-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-neutral-50 text-neutral-600"> <%= t('devise.registrations.new.continue_with') %> </span>
</div>
</div>
<div class="mt-4">
<%= render "devise/shared/links" %>
</div>
</div>
</div> </div>
</div>
<div class="field">
<%= f.label :password %>
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,64 +1,63 @@
<div class="min-h-screen flex items-center justify-center bg-gray-900 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 %>
<img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" /> <img class="mx-auto h-12 w-auto" src="/icon.svg" alt="Aperonight" />
<% end %> <% end %>
<h2 class="mt-6 text-center text-3xl font-extrabold text-white"> <h2 class="mt-6 text-center text-3xl font-extrabold text-neutral-900">
Connexion à votre compte <%= t('devise.sessions.new.title') %>
</h2> </h2>
<p class="mt-2 text-center text-sm text-gray-400"> <p class="mt-2 text-center text-sm text-neutral-600">
Ou <%= t('devise.sessions.new.or') %>
<a href="<%= new_user_registration_path %>" class="font-medium text-indigo-400 hover:text-indigo-300"> <a href="<%= new_user_registration_path %>" class="font-medium text-purple-600 hover:text-purple-500">
créez un nouveau compte <%= t('devise.sessions.new.sign_up_link') %>
</a> </a>
</p> </p>
</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-gray-700 placeholder-gray-500 text-gray-100 bg-gray-800 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm", 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: t('devise.sessions.new.email_placeholder') %>
</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-gray-700 placeholder-gray-500 text-gray-100 bg-gray-800 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm", 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: t('devise.sessions.new.password_placeholder') %>
</div> </div>
</div> </div>
<% if devise_mapping.rememberable? %> <% if devise_mapping.rememberable? %>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<%= f.check_box :remember_me, class: "h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-700 rounded bg-gray-800" %> <%= f.check_box :remember_me, class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-neutral-300 rounded bg-white" %>
<label for="user_remember_me" class="ml-2 block text-sm text-gray-400"> Se souvenir de moi </label> <label for="user_remember_me" class="ml-2 block text-sm text-neutral-700"> <%= t('devise.sessions.new.remember_me') %> </label>
</div> </div>
<div class="text-sm"> <div class="text-sm">
<%= link_to "Mot de passe oublié?", new_password_path(resource_name), class: "font-medium text-indigo-400 hover:text-indigo-300" %> <%= link_to t('devise.sessions.new.forgot_password'), new_password_path(resource_name), class: "font-medium text-purple-600 hover:text-purple-500" %>
</div> </div>
</div> </div>
<% end %> <% end %>
<div class="actions"> <div class="actions">
<%= f.submit "Se connecter", class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500" %> <%= f.submit t('devise.sessions.new.sign_in'), 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 %>
<div class="mt-6"> <div class="mt-6">
<div class="relative"> <div class="relative">
<div class="absolute inset-0 flex items-center"> <div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-700"></div> <div class="w-full border-t border-neutral-300"></div>
</div> </div>
<div class="relative flex justify-center text-sm"> <div class="relative flex justify-center text-sm">
<span class="px-2 bg-gray-900 text-gray-400"> Ou continuer avec </span> <span class="px-2 bg-neutral-50 text-neutral-600"> <%= t('devise.sessions.new.continue_with') %> </span>
</div> </div>
</div> </div>

View File

@@ -1,15 +1,5 @@
<% if resource.errors.any? %> <% if resource.errors.any? %>
<div id="error_explanation" data-turbo-cache="false"> <% resource.errors.full_messages.each do |message| %>
<h2> <% flash.now[:error] = message %>
<%= I18n.t("errors.messages.not_saved", <% end %>
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%>
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %> <% end %>

View File

@@ -1,38 +1,38 @@
<div class=""> <div class="mt-4 space-y-4">
<%- if controller_name != "sessions" %> <%- if controller_name != "sessions" %>
<div class="w-full flex justify-center py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= link_to "Se connecter", new_session_path(resource_name), class: "block" %> <%= link_to t('devise.shared.links.sign_in'), new_session_path(resource_name), class: "block" %>
</div> </div>
<% end %> <% end %>
<%- if devise_mapping.registerable? && controller_name != "registrations" %> <%- if devise_mapping.registerable? && controller_name != "registrations" %>
<div class="w-full flex justify-center mt-4 py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center mt-4 py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= link_to "S'inscrire", new_registration_path(resource_name), class: "" %> <%= link_to t('devise.shared.links.sign_up'), new_registration_path(resource_name), class: "block" %>
</div> </div>
<% end %> <% end %>
<%- if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" %> <%- if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" %>
<div class="w-full flex justify-center mt-4 py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center mt-4 py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= link_to "Mot de passe oublié ?", new_password_path(resource_name), class: "" %> <%= link_to t('devise.shared.links.forgot_password'), new_password_path(resource_name), class: "block" %>
</div> </div>
<% end %> <% end %>
<%- if devise_mapping.confirmable? && controller_name != "confirmations" %> <%- if devise_mapping.confirmable? && controller_name != "confirmations" %>
<div class="w-full flex justify-center mt-4 py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center mt-4 py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= link_to "Vous n'avez pas reçu les instructions de confirmation ?", new_confirmation_path(resource_name), class: "" %> <%= link_to t('devise.shared.links.confirmation_instructions'), new_confirmation_path(resource_name), class: "block" %>
</div> </div>
<% end %> <% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != "unlocks" %> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != "unlocks" %>
<div class="w-full flex justify-center mt-4 py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center mt-4 py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= link_to "Vous n'avez pas reçu les instructions de déverrouillage?", new_unlock_path(resource_name), class: "" %> <%= link_to t('devise.shared.links.unlock_instructions'), new_unlock_path(resource_name), class: "block" %>
</div> </div>
<% end %> <% end %>
<%- if devise_mapping.omniauthable? %> <%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %> <%- resource_class.omniauth_providers.each do |provider| %>
<div class="w-full flex justify-center mt-4 py-2 px-4 border border-gray-700 rounded-md shadow-sm text-sm font-medium text-gray-300 bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-indigo-500"> <div class="w-full flex justify-center mt-4 py-2 px-4 border border-neutral-300 rounded-md shadow-sm text-sm font-medium text-purple-600 bg-white hover:bg-neutral-50 hover:border-purple-500 transition-all duration-200">
<%= button_to "Se connecter avec #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false }, class: "" %> <%= button_to t('devise.shared.links.sign_in_with', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), data: { turbo: false }, class: "block" %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -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 />

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

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

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

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

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

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

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

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html class="h-full bg-neutral-50">
<head> <head>
<title><%= content_for(:title) || "Aperonight" %></title> <title><%= content_for(:title) || "Aperonight" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -7,33 +7,38 @@
<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" %>
<%= 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> <div class="">
<div id="header">
<%= render "components/header" %> <%= render "components/header" %>
</div><!-- /#header -->
<main> <main class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<%= yield %> <div class="flash mx-auto px-4 sm:px-6 lg:px-8 py-8">
</main> <%= render "shared/flash_messages" %>
</div>
<div class="yield">
<%= yield %>
</div>
</main>
<footer class="bg-neutral-100 text-neutral-600">
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
<%= render "components/footer" %>
</div>
</footer>
<div id="footer">
<%= render "components/footer" %>
</div> </div>
</body> </body>
</html> </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,46 @@
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">Upcoming Events</h1>
<% if @parties.any? %>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<% @parties.each do |party| %>
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<% if party.image.present? %>
<img src="<%= party.image %>" alt="<%= party.name %>" class="w-full h-48 object-cover">
<% else %>
<div class="bg-gray-200 border-2 border-dashed rounded-xl w-full h-48 flex items-center justify-center">
<span class="text-gray-500">No Image</span>
</div>
<% end %>
<div class="p-6">
<h2 class="text-xl font-semibold mb-2"><%= party.name %></h2>
<p class="text-gray-600 mb-4"><%= party.description.truncate(100) %></p>
<div class="flex justify-between items-center">
<div>
<p class="text-sm text-gray-500"><%= party.venue_name %></p>
<p class="text-sm text-gray-500"><%= party.venue_address %></p>
</div>
<div>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
<%= party.state.humanize %>
</span>
</div>
</div>
</div>
</div>
<% end %>
</div>
<!-- Pagination -->
<div class="mt-8 flex justify-center">
<%= paginate @parties %>
</div>
<% else %>
<div class="text-center py-12">
<h2 class="text-xl font-medium text-gray-900 mb-4">No events found</h2>
<p class="text-gray-500">Check back later for upcoming events.</p>
</div>
<% end %>
</div>

View File

@@ -1,31 +1,31 @@
<!-- Hero Section --> <!-- Hero Section -->
<section class="relative bg-gradient-to-br from-indigo-900 via-purple-800 to-pink-700 min-h-[70vh] flex items-center"> <section class="relative bg-neutral-50 min-h-[70vh] flex items-center">
<div class="absolute inset-0 bg-black bg-opacity-40"></div> <div class="absolute inset-0 bg-white bg-opacity-60"></div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 class="text-5xl md:text-7xl font-bold text-white mb-6 leading-tight"> <h1 class="text-5xl md:text-7xl font-bold text-neutral-900 mb-6 leading-tight">
Découvrez les afterworks et soirée Découvrez les afterworks et soirée
<span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-400"> <span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-pink-600">
à Paris à Paris
</span> </span>
</h1> </h1>
<p class="text-xl md:text-2xl text-gray-200 mb-8 max-w-3xl mx-auto leading-relaxed"> <p class="text-xl md:text-2xl text-neutral-700 mb-8 max-w-3xl mx-auto leading-relaxed">
Les meilleures soirées, concerts et afterworks de Paris en un clic. Réservez vos places et vivez des expériences uniques. Les meilleures soirées, concerts et afterworks de Paris en un clic. Réservez vos places et vivez des expériences uniques.
</p> </p>
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center"> <div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
<%= link_to "Explorer les soirées", "#events", class: "bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-4 px-8 rounded-full transition-all duration-300 transform hover:scale-105 shadow-lg" %> <%= link_to "Explorer les soirées", parties_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 transition-all duration-300 transform hover:scale-105 shadow-lg" %>
<%= link_to "Voir les concerts", "#", class: "bg-white bg-opacity-10 backdrop-blur-sm border border-white border-opacity-30 hover:bg-opacity-20 text-black font-semibold py-4 px-8 rounded-full transition-all duration-300" %> <%= link_to "Voir les concerts", "#", class: "bg-white border border-neutral-300 hover:border-purple-300 text-neutral-700 font-semibold py-4 px-8 rounded-full transition-all duration-300" %>
</div> </div>
</div> </div>
</section> </section>
<!-- Metrics --> <!-- Metrics -->
<section class="bg-gradient-to-br from-gray-900 via-gray-800 to-black py-20"> <section class="bg-neutral-50 py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16"> <div class="text-center mb-16">
<h2 class="text-4xl md:text-5xl font-bold text-white mb-4"> <h2 class="text-4xl md:text-5xl font-bold text-neutral-900 mb-4">
Des chiffres qui parlent Des chiffres qui parlent
</h2> </h2>
<p class="text-xl text-gray-300 max-w-2xl mx-auto"> <p class="text-xl text-neutral-600 max-w-2xl mx-auto">
La plateforme préférée des Parisiens pour vivre la nuit La plateforme préférée des Parisiens pour vivre la nuit
</p> </p>
</div> </div>
@@ -33,13 +33,13 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12"> <div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
<!-- Total Events --> <!-- Total Events -->
<div class="group relative"> <div class="group relative">
<div class="relative overflow-hidden rounded-2xl bg-gray-800/60 backdrop-blur-sm border border-gray-700/50 hover:border-purple-500/50 transition-all duration-300 p-8"> <div class="relative overflow-hidden rounded-2xl bg-white border border-neutral-200 hover:border-purple-300 transition-all duration-300 p-8">
<div class="absolute inset-0 bg-gradient-to-br from-purple-600/20 to-indigo-600/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<div class="relative"> <div class="relative">
<div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-400 via-indigo-400 to-pink-400 bg-clip-text text-transparent mb-3"> <div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
<span class="counter" data-controller="counter" data-counter-target-value="127">0</span> <span class="counter" data-controller="counter" data-counter-target-value="127">0</span>
</div> </div>
<p class="text-gray-200 font-mono uppercase tracking-widest text-sm font-medium"> <p class="text-neutral-700 font-mono uppercase tracking-widest text-sm font-medium">
Événements organisés Événements organisés
</p> </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 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>
@@ -49,13 +49,13 @@
<!-- Total Users --> <!-- Total Users -->
<div class="group relative"> <div class="group relative">
<div class="relative overflow-hidden rounded-2xl bg-gray-800/60 backdrop-blur-sm border border-gray-700/50 hover:border-purple-500/50 transition-all duration-300 p-8"> <div class="relative overflow-hidden rounded-2xl bg-white border border-neutral-200 hover:border-purple-300 transition-all duration-300 p-8">
<div class="absolute inset-0 bg-gradient-to-br from-purple-600/20 to-indigo-600/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<div class="relative"> <div class="relative">
<div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-400 via-indigo-400 to-pink-400 bg-clip-text text-transparent mb-3"> <div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
<span class="counter" data-controller="counter" data-counter-target-value="1433">0</span>+ <span class="counter" data-controller="counter" data-counter-target-value="1433">0</span>+
</div> </div>
<p class="text-gray-200 font-mono uppercase tracking-widest text-sm font-medium"> <p class="text-neutral-700 font-mono uppercase tracking-widest text-sm font-medium">
Membres actifs Membres actifs
</p> </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 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>
@@ -65,13 +65,13 @@
<!-- Average Rating --> <!-- Average Rating -->
<div class="group relative"> <div class="group relative">
<div class="relative overflow-hidden rounded-2xl bg-gray-800/60 backdrop-blur-sm border border-gray-700/50 hover:border-purple-500/50 transition-all duration-300 p-8"> <div class="relative overflow-hidden rounded-2xl bg-white border border-neutral-200 hover:border-purple-300 transition-all duration-300 p-8">
<div class="absolute inset-0 bg-gradient-to-br from-purple-600/20 to-indigo-600/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> <div class="absolute inset-0 bg-gradient-to-br from-purple-100 to-indigo-100 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<div class="relative"> <div class="relative">
<div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-400 via-indigo-400 to-pink-400 bg-clip-text text-transparent mb-3"> <div class="text-5xl md:text-6xl font-light bg-gradient-to-r from-purple-600 via-indigo-600 to-pink-600 bg-clip-text text-transparent mb-3">
<span class="counter" data-controller="counter" data-counter-target-value="4.4" data-counter-decimal-value="true">0</span>/5 <span class="counter" data-controller="counter" data-counter-target-value="4.4" data-counter-decimal-value="true">0</span>/5
</div> </div>
<p class="text-gray-200 font-mono uppercase tracking-widest text-sm font-medium"> <p class="text-neutral-700 font-mono uppercase tracking-widest text-sm font-medium">
Note moyenne des soirées Note moyenne des soirées
</p> </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 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>
@@ -83,51 +83,51 @@
<!-- Additional Stats Row --> <!-- Additional Stats Row -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-6 mt-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 mt-12">
<div class="text-center"> <div class="text-center">
<div class="text-3xl font-bold text-purple-300"> <div class="text-3xl font-bold text-purple-600">
<span class="counter" data-controller="counter" data-counter-target-value="79">0</span>% <span class="counter" data-controller="counter" data-counter-target-value="79">0</span>%
</div> </div>
<p class="text-gray-300 text-sm font-mono uppercase tracking-wide font-medium">Taux de remplissage</p> <p class="text-neutral-600 text-sm font-mono uppercase tracking-wide font-medium">Taux de remplissage</p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-3xl font-bold text-purple-300"> <div class="text-3xl font-bold text-purple-600">
<span class="counter" data-controller="counter" data-counter-target-value="12">0</span> <span class="counter" data-controller="counter" data-counter-target-value="12">0</span>
</div> </div>
<p class="text-gray-300 text-sm font-mono uppercase tracking-wide font-medium">Arrondissements</p> <p class="text-neutral-600 text-sm font-mono uppercase tracking-wide font-medium">Arrondissements</p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-3xl font-bold text-purple-300"> <div class="text-3xl font-bold text-purple-600">
<span class="counter" data-controller="counter" data-counter-target-value="156">0</span> <span class="counter" data-controller="counter" data-counter-target-value="156">0</span>
</div> </div>
<p class="text-gray-300 text-sm font-mono uppercase tracking-wide font-medium">Établissements partenaires</p> <p class="text-neutral-600 text-sm font-mono uppercase tracking-wide font-medium">Établissements partenaires</p>
</div> </div>
<div class="text-center"> <div class="text-center">
<div class="text-3xl font-bold text-purple-300"> <div class="text-3xl font-bold text-purple-600">
<span class="counter" data-controller="counter" data-counter-target-value="98">0</span>% <span class="counter" data-controller="counter" data-counter-target-value="98">0</span>%
</div> </div>
<p class="text-gray-300 text-sm font-mono uppercase tracking-wide font-medium">Satisfaction client</p> <p class="text-neutral-600 text-sm font-mono uppercase tracking-wide font-medium">Satisfaction client</p>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<!-- Quick Search Section --> <!-- Quick Search Section -->
<section id="search" class="bg-gray-900 py-16"> <section id="search" class="bg-white py-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl p-8 shadow-2xl"> <div class="bg-gradient-to-br from-neutral-50 to-white border border-neutral-200 rounded-2xl p-8 shadow-lg">
<h2 class="text-3xl font-bold text-white text-center mb-8">Trouvez votre prochaine soirée</h2> <h2 class="text-3xl font-bold text-neutral-900 text-center mb-8">Trouvez votre prochaine soirée</h2>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end"> <div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-2">Quand ?</label> <label class="block text-sm font-medium text-neutral-700 mb-2">Quand ?</label>
<input type="date" class="w-full bg-gray-800 border border-gray-700 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" placeholder="Choisir une date"> <input type="date" class="w-full bg-white border border-neutral-300 text-neutral-900 rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" placeholder="Choisir une date">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-2">Type d'événement</label> <label class="block text-sm font-medium text-neutral-700 mb-2">Type d'événement</label>
<select class="w-full bg-gray-800 border border-gray-700 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all"> <select class="w-full bg-white border border-neutral-300 text-neutral-900 rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all">
<option value="">Tous les types</option> <option value="">Tous les types</option>
<option value="club">Soirées club</option> <option value="club">Soirées club</option>
<option value="afterwork">Afterworks</option> <option value="afterwork">Afterworks</option>
@@ -135,10 +135,10 @@
<option value="vip">Événements VIP</option> <option value="vip">Événements VIP</option>
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-2">Genre musical</label> <label class="block text-sm font-medium text-neutral-700 mb-2">Genre musical</label>
<select class="w-full bg-gray-800 border border-gray-700 text-white rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all"> <select class="w-full bg-white border border-neutral-300 text-neutral-900 rounded-lg p-3 focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all">
<option value="">Tous les genres</option> <option value="">Tous les genres</option>
<option value="house">House/Techno</option> <option value="house">House/Techno</option>
<option value="hiphop">Hip-Hop</option> <option value="hiphop">Hip-Hop</option>
@@ -147,7 +147,7 @@
<option value="electro">Électro</option> <option value="electro">Électro</option>
</select> </select>
</div> </div>
<button class="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-105"> <button class="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-105">
Rechercher Rechercher
</button> </button>
@@ -157,89 +157,51 @@
</section> </section>
<!-- Featured Events --> <!-- Featured Events -->
<section id="events" class="bg-gradient-to-b from-gray-900 to-black py-20"> <section id="events" class="bg-neutral-50 py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-4xl font-bold text-white mb-4">Événements du moment</h2> <h2 class="text-4xl font-bold text-neutral-900 mb-4">Événements du moment</h2>
<p class="text-xl text-gray-400">Les soirées et concerts les plus populaires cette semaine</p> <p class="text-xl text-neutral-600">Les soirées et concerts les plus populaires cette semaine</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Event Card 1 --> <% @parties.each do |party| %>
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl overflow-hidden hover:transform hover:scale-105 transition-all duration-300 shadow-xl"> <div class="bg-white border border-neutral-200 rounded-2xl overflow-hidden hover:transform hover:scale-105 transition-all duration-300 shadow-lg">
<div class="h-56 bg-gradient-to-br from-purple-500 via-pink-500 to-red-500 relative"> <div class="h-56 bg-gradient-to-br from-purple-500 via-pink-500 to-red-500 relative"
<div class="absolute top-4 right-4 bg-black bg-opacity-60 text-white px-3 py-1 rounded-full text-sm font-medium"> style="background-image: url('<%= party.image || "https://images.unsplash.com/photo-1506157786151-b84b9d3d78d8?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80" %>');
Club background-size: cover;
background-position: center;">
<div class="absolute top-4 right-4 bg-white bg-opacity-90 text-neutral-900 px-3 py-1 rounded-full text-sm font-medium">
<%= party.venue_name.split(' ').first %>
</div>
</div>
<div class="p-6">
<div class="flex justify-between items-start mb-3">
<h3 class="text-2xl font-bold text-neutral-900"><%= party.name.upcase %></h3>
<% if party.ticket_types.any? %>
<span class="text-purple-600 font-semibold"><%= number_to_currency(party.ticket_types.first.price_cents / 100.0, unit: "€", separator: ",", delimiter: " ") %></span>
<% end %>
</div>
<p class="text-neutral-600 mb-2"><%= party.venue_name %>, <%= party.venue_address.split(',').first %></p>
<p class="text-neutral-700 mb-4"><%= I18n.l(party.start_time, format: "%A %Hh") if party.start_time %> • <%= party.description.split('.').first %></p>
<%= link_to "Voir les détails", party_path(party.slug, party), class: "w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-3 rounded-lg transition-all duration-300 text-center block" %>
</div> </div>
</div> </div>
<div class="p-6"> <% end %>
<div class="flex justify-between items-start mb-3">
<h3 class="text-2xl font-bold text-white">TECHNO NIGHT</h3>
<span class="text-purple-400 font-semibold">25€</span>
</div>
<p class="text-gray-400 mb-2">Rex Club, Paris 2ème</p>
<p class="text-gray-300 mb-4">Vendredi 22h • Soirée techno underground</p>
<button class="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-3 rounded-lg transition-all duration-300">
Réserver ma place
</button>
</div>
</div>
<!-- Event Card 2 -->
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl overflow-hidden hover:transform hover:scale-105 transition-all duration-300 shadow-xl">
<div class="h-56 bg-gradient-to-br from-blue-500 via-cyan-500 to-teal-500 relative">
<div class="absolute top-4 right-4 bg-black bg-opacity-60 text-white px-3 py-1 rounded-full text-sm font-medium">
Afterwork
</div>
</div>
<div class="p-6">
<div class="flex justify-between items-start mb-3">
<h3 class="text-2xl font-bold text-white">SUNSET APÉRO</h3>
<span class="text-blue-400 font-semibold">15€</span>
</div>
<p class="text-gray-400 mb-2">Nuba, Paris 13ème</p>
<p class="text-gray-300 mb-4">Jeudi 18h • Apéro sur les quais</p>
<button class="w-full bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white font-semibold py-3 rounded-lg transition-all duration-300">
Réserver ma place
</button>
</div>
</div>
<!-- Event Card 3 -->
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl overflow-hidden hover:transform hover:scale-105 transition-all duration-300 shadow-xl">
<div class="h-56 bg-gradient-to-br from-green-500 via-emerald-500 to-teal-500 relative">
<div class="absolute top-4 right-4 bg-black bg-opacity-60 text-white px-3 py-1 rounded-full text-sm font-medium">
Concert
</div>
</div>
<div class="p-6">
<div class="flex justify-between items-start mb-3">
<h3 class="text-2xl font-bold text-white">LIVE SESSION</h3>
<span class="text-green-400 font-semibold">20€</span>
</div>
<p class="text-gray-400 mb-2">La Bellevilloise, Paris 20ème</p>
<p class="text-gray-300 mb-4">Samedi 20h • Concert live acoustique</p>
<button class="w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white font-semibold py-3 rounded-lg transition-all duration-300">
Réserver ma place
</button>
</div>
</div>
</div> </div>
<div class="text-center mt-12"> <div class="text-center mt-12">
<button class="text-purple-400 hover:text-purple-300 text-lg font-medium transition-all duration-300 border-b-2 border-purple-400 hover:border-purple-300"> <%= link_to "Voir tous les événements →", parties_path, class: "text-purple-600 hover:text-purple-700 text-lg font-medium transition-all duration-300 border-b-2 border-purple-600 hover:border-purple-700" %>
Voir tous les événements →
</button>
</div> </div>
</div> </div>
</section> </section>
<!-- Features --> <!-- Features -->
<section class="bg-gray-900 py-20"> <section class="bg-white py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16"> <div class="text-center mb-16">
<h2 class="text-4xl font-bold text-white mb-4">Pourquoi choisir <%= Rails.application.config.app_name %> ?</h2> <h2 class="text-4xl font-bold text-neutral-900 mb-4">Pourquoi choisir <%= Rails.application.config.app_name %> ?</h2>
<p class="text-xl text-gray-400 max-w-2xl mx-auto">La plateforme préférée des Parisiens pour sortir</p> <p class="text-xl text-neutral-600 max-w-2xl mx-auto">La plateforme préférée des Parisiens pour sortir</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
@@ -249,8 +211,8 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg> </svg>
</div> </div>
<h3 class="text-2xl font-bold text-white mb-3">Découverte facile</h3> <h3 class="text-2xl font-bold text-neutral-900 mb-3">Découverte facile</h3>
<p class="text-gray-400 leading-relaxed">Trouvez les meilleures soirées et concerts de Paris en quelques clics grâce à notre algorithme personnalisé</p> <p class="text-neutral-600 leading-relaxed">Trouvez les meilleures soirées et concerts de Paris en quelques clics grâce à notre algorithme personnalisé</p>
</div> </div>
<div class="text-center p-8"> <div class="text-center p-8">
@@ -259,8 +221,8 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
<h3 class="text-2xl font-bold text-white mb-3">Réservation sécurisée</h3> <h3 class="text-2xl font-bold text-neutral-900 mb-3">Réservation sécurisée</h3>
<p class="text-gray-400 leading-relaxed">Paiement 100% sécurisé et billets électroniques avec QR code sur votre mobile</p> <p class="text-neutral-600 leading-relaxed">Paiement 100% sécurisé et billets électroniques avec QR code sur votre mobile</p>
</div> </div>
<div class="text-center p-8"> <div class="text-center p-8">
@@ -269,20 +231,20 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg> </svg>
</div> </div>
<h3 class="text-2xl font-bold text-white mb-3">Accès rapide</h3> <h3 class="text-2xl font-bold text-neutral-900 mb-3">Accès rapide</h3>
<p class="text-gray-400 leading-relaxed">Entrée express avec validation mobile de vos billets. Plus besoin d'imprimer !</p> <p class="text-neutral-600 leading-relaxed">Entrée express avec validation mobile de vos billets. Plus besoin d'imprimer !</p>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<!-- CTA Section --> <!-- CTA Section -->
<section class="bg-gradient-to-r from-purple-900 via-indigo-900 to-pink-900 py-20"> <section class="bg-gradient-to-r from-purple-100 via-pink-50 to-indigo-100 py-20">
<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-white 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-gray-300 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>

View File

@@ -1,2 +1,47 @@
<h1>Pages#legals</h1> <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<p>Find me in app/views/pages/legals.html.erb</p> <h1 class="text-4xl font-bold text-neutral-900 mb-8">Mentions légales</h1>
<div class="prose prose-neutral max-w-none">
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Éditeur du site</h2>
<p class="text-neutral-700 mb-6">
Le présent site est édité par Aperonight, une société par actions simplifiée au capital de 1 000 €,
immatriculée au Registre du Commerce et des Sociétés de Paris sous le numéro 123 456 789 000 00,
dont le siège social est situé à 123 rue de Paris, 75000 Paris.
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Directeur de la publication</h2>
<p class="text-neutral-700 mb-6">
Le directeur de la publication est M. Jean Dupont, en tant que Président d'Aperonight.
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Hébergement</h2>
<p class="text-neutral-700 mb-6">
Le site est hébergé par Heroku Inc., 415 Mission Street, Suite 300, San Francisco, CA 94105, États-Unis.
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Propriété intellectuelle</h2>
<p class="text-neutral-700 mb-6">
L'ensemble du contenu du site (textes, images, logos, vidéos) est la propriété exclusive d'Aperonight
et est protégé par les lois françaises et internationales relatives à la propriété intellectuelle.
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Données personnelles</h2>
<p class="text-neutral-700 mb-6">
Conformément à la réglementation européenne sur la protection des données (RGPD), vous disposez d'un droit
d'accès, de rectification et de suppression des données vous concernant. Pour exercer ces droits, contactez-nous
à l'adresse email suivante : privacy@aperonight.com
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Cookies</h2>
<p class="text-neutral-700 mb-6">
Le site utilise des cookies pour améliorer l'expérience utilisateur. En continuant à utiliser le site,
vous acceptez l'utilisation de ces cookies conformément à notre politique de confidentialité.
</p>
<h2 class="text-2xl font-semibold text-neutral-900 mb-4">Contact</h2>
<p class="text-neutral-700 mb-6">
Pour toute question concernant le site ou ses mentions légales, vous pouvez nous contacter à l'adresse suivante :<br>
<a href="mailto:legal@aperonight.com" class="text-purple-600 hover:text-purple-500">legal@aperonight.com</a>
</p>
</div>
</div>

View File

@@ -0,0 +1,53 @@
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<h1 class="text-3xl font-bold text-gray-900 mb-8">Événements à venir</h1>
<% if @parties.any? %>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<% @parties.each do |party| %>
<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
<div class="p-6">
<div class="flex justify-between items-start">
<div>
<h2 class="text-xl font-bold text-gray-900"><%= party.name %></h2>
<p class="text-sm text-gray-500 mt-1"><%= party.user.email %></p>
</div>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
<%= party.start_time.strftime("%d/%m/%Y") %>
</span>
</div>
<div class="mt-4">
<p class="text-gray-600 text-sm line-clamp-2"><%= party.description.truncate(100) %></p>
</div>
<div class="mt-4 flex justify-between items-center">
<div>
<% if party.ticket_types.any? %>
<p class="text-sm font-medium text-gray-900">
À partir de <%= format_price(party.ticket_types.minimum(:price_cents)) %>€
</p>
<% else %>
<p class="text-sm text-gray-500">Pas de billets disponibles</p>
<% end %>
</div>
<%= link_to "Voir les détails", party_path(party.slug, party), class: "inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-full shadow-sm text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500" %>
</div>
</div>
</div>
<% end %>
</div>
<div class="mt-8">
<%# paginate @parties, theme: 'twitter_bootstrap' %>
<%= paginate @parties %>
</div>
<% else %>
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900">Aucun événement disponible</h3>
<p class="mt-1 text-sm text-gray-500">Il n'y a aucun événement à venir pour le moment.</p>
</div>
<% end %>

View File

@@ -0,0 +1,37 @@
<div class="container mt-4">
<div class="row">
<div class="col-md-8">
<h1><%= @party.name %></h1>
<% if @party.image.present? %>
<%= image_tag @party.image, class: "img-fluid rounded mb-3" %>
<% end %>
<p><%= @party.description %></p>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Event Details</h5>
<p class="card-text">
<strong>Venue:</strong> <%= @party.venue_name %><br>
<strong>Address:</strong> <%= @party.venue_address %><br>
<strong>Start Time:</strong> <%= @party.start_time.strftime("%B %d, %Y at %I:%M %p") %><br>
<strong>End Time:</strong> <%= @party.end_time.strftime("%B %d, %Y at %I:%M %p") %>
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Ticket Information</h5>
<p class="card-text">
<!-- Ticket types would go here -->
<a href="#" class="btn btn-primary">Get Tickets</a>
</p>
</div>
</div>
</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

648
bun.lock Normal file
View File

@@ -0,0 +1,648 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "app",
"dependencies": {
"@hotwired/stimulus": "^3.2.2",
"@hotwired/turbo-rails": "^8.0.13",
"@radix-ui/react-slot": "^1.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
"@types/alpinejs": "^3.13.11",
"alpinejs": "^3.14.9",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cssnano": "^7.0.6",
"esbuild": "^0.25.4",
"pm2": "^6.0.5",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^16.1.0",
"postcss-nested": "^7.0.2",
"postcss-nesting": "^13.0.1",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.4",
"tailwindcss-animate": "^1.0.7",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@csstools/selector-resolve-nested": ["@csstools/selector-resolve-nested@3.1.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g=="],
"@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
"@hotwired/stimulus": ["@hotwired/stimulus@3.2.2", "", {}, "sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A=="],
"@hotwired/turbo": ["@hotwired/turbo@8.0.13", "", {}, "sha512-M7qXUqcGab6G5PKOiwhgbByTtrPgKPFCTMNQ52QhzUEXEqmp0/ApEguUesh/FPiUjrmFec+3lq98KsWnYY2C7g=="],
"@hotwired/turbo-rails": ["@hotwired/turbo-rails@8.0.16", "", { "dependencies": { "@hotwired/turbo": "^8.0.13", "@rails/actioncable": ">=7.0" } }, "sha512-Yxiy2x+N3eOIEDokvLzSrd08aI5RDKnFYDQFl2J/LuMEWTtPdY7oNP0F/gv/sSe5AV23Lwz4FitG/uNFXNM5tA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
"@pm2/agent": ["@pm2/agent@2.1.1", "", { "dependencies": { "async": "~3.2.0", "chalk": "~3.0.0", "dayjs": "~1.8.24", "debug": "~4.3.1", "eventemitter2": "~5.0.1", "fast-json-patch": "^3.1.0", "fclone": "~1.0.11", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.0", "proxy-agent": "~6.4.0", "semver": "~7.5.0", "ws": "~7.5.10" } }, "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ=="],
"@pm2/io": ["@pm2/io@6.1.0", "", { "dependencies": { "async": "~2.6.1", "debug": "~4.3.1", "eventemitter2": "^6.3.1", "require-in-the-middle": "^5.0.0", "semver": "~7.5.4", "shimmer": "^1.2.0", "signal-exit": "^3.0.3", "tslib": "1.9.3" } }, "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ=="],
"@pm2/js-api": ["@pm2/js-api@0.8.0", "", { "dependencies": { "async": "^2.6.3", "debug": "~4.3.1", "eventemitter2": "^6.3.1", "extrareqp2": "^1.0.0", "ws": "^7.0.0" } }, "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA=="],
"@pm2/pm2-version-check": ["@pm2/pm2-version-check@1.0.4", "", { "dependencies": { "debug": "^4.3.1" } }, "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA=="],
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@rails/actioncable": ["@rails/actioncable@8.0.201", "", {}, "sha512-WiXZodvnK7u+wlu72DZydfV75x14HhzXI84sto9xcdsW1DMOHK+jYwQuuE/Wh/hKH5yajFIw/3DUP6MHDeGrbA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.12" } }, "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.12", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.12", "@tailwindcss/oxide-darwin-arm64": "4.1.12", "@tailwindcss/oxide-darwin-x64": "4.1.12", "@tailwindcss/oxide-freebsd-x64": "4.1.12", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", "@tailwindcss/oxide-linux-x64-musl": "4.1.12", "@tailwindcss/oxide-wasm32-wasi": "4.1.12", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.12", "", { "os": "android", "cpu": "arm64" }, "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12", "", { "os": "linux", "cpu": "arm" }, "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.12", "", { "cpu": "none" }, "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.12", "", { "os": "win32", "cpu": "x64" }, "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.12", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.12", "@tailwindcss/oxide": "4.1.12", "postcss": "^8.4.41", "tailwindcss": "4.1.12" } }, "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ=="],
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@types/alpinejs": ["@types/alpinejs@3.13.11", "", {}, "sha512-3KhGkDixCPiLdL3Z/ok1GxHwLxEWqQOKJccgaQL01wc0EVM2tCTaqlC3NIedmxAXkVzt/V6VTM8qPgnOHKJ1MA=="],
"@vue/reactivity": ["@vue/reactivity@3.1.5", "", { "dependencies": { "@vue/shared": "3.1.5" } }, "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg=="],
"@vue/shared": ["@vue/shared@3.1.5", "", {}, "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"alpinejs": ["alpinejs@3.14.9", "", { "dependencies": { "@vue/reactivity": "~3.1.1" } }, "sha512-gqSOhTEyryU9FhviNqiHBHzgjkvtukq9tevew29fTj+ofZtfsYriw4zPirHHOAy9bw8QoL3WGhyk7QqCh5AYlw=="],
"amp": ["amp@0.3.1", "", {}, "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw=="],
"amp-message": ["amp-message@0.1.2", "", { "dependencies": { "amp": "0.3.1" } }, "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ansis": ["ansis@4.0.0-node10", "", {}, "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"blessed": ["blessed@0.1.81", "", { "bin": "bin/tput.js" }, "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ=="],
"bodec": ["bodec@0.1.0", "", {}, "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["browserslist@4.25.2", "", { "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": "cli.js" }, "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="],
"caniuse-lite": ["caniuse-lite@1.0.30001735", "", {}, "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w=="],
"chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"charm": ["charm@0.1.2", "", {}, "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
"cli-tableau": ["cli-tableau@2.0.1", "", { "dependencies": { "chalk": "3.0.0" } }, "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
"commander": ["commander@2.15.1", "", {}, "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="],
"croner": ["croner@4.1.97", "", {}, "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ=="],
"css-declaration-sorter": ["css-declaration-sorter@7.2.0", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow=="],
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"cssnano": ["cssnano@7.1.0", "", { "dependencies": { "cssnano-preset-default": "^7.0.8", "lilconfig": "^3.1.3" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w=="],
"cssnano-preset-default": ["cssnano-preset-default@7.0.8", "", { "dependencies": { "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.1", "postcss-calc": "^10.1.1", "postcss-colormin": "^7.0.4", "postcss-convert-values": "^7.0.6", "postcss-discard-comments": "^7.0.4", "postcss-discard-duplicates": "^7.0.2", "postcss-discard-empty": "^7.0.1", "postcss-discard-overridden": "^7.0.1", "postcss-merge-longhand": "^7.0.5", "postcss-merge-rules": "^7.0.6", "postcss-minify-font-values": "^7.0.1", "postcss-minify-gradients": "^7.0.1", "postcss-minify-params": "^7.0.4", "postcss-minify-selectors": "^7.0.5", "postcss-normalize-charset": "^7.0.1", "postcss-normalize-display-values": "^7.0.1", "postcss-normalize-positions": "^7.0.1", "postcss-normalize-repeat-style": "^7.0.1", "postcss-normalize-string": "^7.0.1", "postcss-normalize-timing-functions": "^7.0.1", "postcss-normalize-unicode": "^7.0.4", "postcss-normalize-url": "^7.0.1", "postcss-normalize-whitespace": "^7.0.1", "postcss-ordered-values": "^7.0.2", "postcss-reduce-initial": "^7.0.4", "postcss-reduce-transforms": "^7.0.1", "postcss-svgo": "^7.1.0", "postcss-unique-selectors": "^7.0.4" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ=="],
"cssnano-utils": ["cssnano-utils@5.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg=="],
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
"culvert": ["culvert@0.1.2", "", {}, "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg=="],
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
"dependency-graph": ["dependency-graph@1.0.0", "", {}, "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"electron-to-chromium": ["electron-to-chromium@1.5.203", "", {}, "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
"enquirer": ["enquirer@2.3.6", "", { "dependencies": { "ansi-colors": "^4.1.1" } }, "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/linux-x64": "0.25.9" }, "bin": "bin/esbuild" }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", "esgenerate": "bin/esgenerate.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"eventemitter2": ["eventemitter2@5.0.1", "", {}, "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg=="],
"extrareqp2": ["extrareqp2@1.0.0", "", { "dependencies": { "follow-redirects": "^1.14.0" } }, "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA=="],
"fast-json-patch": ["fast-json-patch@3.1.1", "", {}, "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ=="],
"fclone": ["fclone@1.0.11", "", {}, "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
"fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
"git-node-fs": ["git-node-fs@1.0.0", "", {}, "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ=="],
"git-sha1": ["git-sha1@0.1.2", "", {}, "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"jiti": ["jiti@2.5.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
"js-git": ["js-git@0.7.8", "", { "dependencies": { "bodec": "^0.1.0", "culvert": "^0.1.2", "git-sha1": "^0.1.2", "pako": "^0.2.5" } }, "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
"lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
"mkdirp": ["mkdirp@1.0.4", "", { "bin": "bin/cmd.js" }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
"module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"needle": ["needle@2.4.0", "", { "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" }, "bin": "bin/needle" }, "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pidusage": ["pidusage@3.0.2", "", { "dependencies": { "safe-buffer": "^5.2.1" } }, "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"pm2": ["pm2@6.0.8", "", { "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "latest", "ansis": "4.0.0-node10", "async": "~3.2.6", "blessed": "0.1.81", "chokidar": "^3.5.3", "cli-tableau": "^2.0.0", "commander": "2.15.1", "croner": "~4.1.92", "dayjs": "~1.11.13", "debug": "^4.3.7", "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", "js-yaml": "~4.1.0", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "~3.0", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", "promptly": "^2", "semver": "^7.6.2", "source-map-support": "0.5.21", "sprintf-js": "1.1.2", "vizion": "~2.2.1" }, "optionalDependencies": { "pm2-sysmonit": "^1.2.8" }, "bin": { "pm2": "bin/pm2", "pm2-dev": "bin/pm2-dev", "pm2-docker": "bin/pm2-docker", "pm2-runtime": "bin/pm2-runtime" } }, "sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw=="],
"pm2-axon": ["pm2-axon@4.0.1", "", { "dependencies": { "amp": "~0.3.1", "amp-message": "~0.1.1", "debug": "^4.3.1", "escape-string-regexp": "^4.0.0" } }, "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg=="],
"pm2-axon-rpc": ["pm2-axon-rpc@0.7.1", "", { "dependencies": { "debug": "^4.3.1" } }, "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw=="],
"pm2-deploy": ["pm2-deploy@1.0.2", "", { "dependencies": { "run-series": "^1.1.8", "tv4": "^1.3.0" } }, "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg=="],
"pm2-multimeter": ["pm2-multimeter@0.1.2", "", { "dependencies": { "charm": "~0.1.1" } }, "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA=="],
"pm2-sysmonit": ["pm2-sysmonit@1.2.8", "", { "dependencies": { "async": "^3.2.0", "debug": "^4.3.1", "pidusage": "^2.0.21", "systeminformation": "^5.7", "tx2": "~1.0.4" } }, "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.38" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="],
"postcss-cli": ["postcss-cli@11.0.1", "", { "dependencies": { "chokidar": "^3.3.0", "dependency-graph": "^1.0.0", "fs-extra": "^11.0.0", "picocolors": "^1.0.0", "postcss-load-config": "^5.0.0", "postcss-reporter": "^7.0.0", "pretty-hrtime": "^1.0.3", "read-cache": "^1.0.0", "slash": "^5.0.0", "tinyglobby": "^0.2.12", "yargs": "^17.0.0" }, "peerDependencies": { "postcss": "^8.0.0" }, "bin": { "postcss": "index.js" } }, "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g=="],
"postcss-colormin": ["postcss-colormin@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw=="],
"postcss-convert-values": ["postcss-convert-values@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ=="],
"postcss-discard-comments": ["postcss-discard-comments@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg=="],
"postcss-discard-duplicates": ["postcss-discard-duplicates@7.0.2", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w=="],
"postcss-discard-empty": ["postcss-discard-empty@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg=="],
"postcss-discard-overridden": ["postcss-discard-overridden@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg=="],
"postcss-flexbugs-fixes": ["postcss-flexbugs-fixes@5.0.2", "", { "peerDependencies": { "postcss": "^8.1.4" } }, "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ=="],
"postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="],
"postcss-load-config": ["postcss-load-config@5.1.0", "", { "dependencies": { "lilconfig": "^3.1.1", "yaml": "^2.4.2" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1" }, "optionalPeers": ["tsx"] }, "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA=="],
"postcss-merge-longhand": ["postcss-merge-longhand@7.0.5", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^7.0.5" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw=="],
"postcss-merge-rules": ["postcss-merge-rules@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ=="],
"postcss-minify-font-values": ["postcss-minify-font-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ=="],
"postcss-minify-gradients": ["postcss-minify-gradients@7.0.1", "", { "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A=="],
"postcss-minify-params": ["postcss-minify-params@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw=="],
"postcss-minify-selectors": ["postcss-minify-selectors@7.0.5", "", { "dependencies": { "cssesc": "^3.0.0", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug=="],
"postcss-nested": ["postcss-nested@7.0.2", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw=="],
"postcss-nesting": ["postcss-nesting@13.0.2", "", { "dependencies": { "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ=="],
"postcss-normalize-charset": ["postcss-normalize-charset@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ=="],
"postcss-normalize-display-values": ["postcss-normalize-display-values@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ=="],
"postcss-normalize-positions": ["postcss-normalize-positions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ=="],
"postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ=="],
"postcss-normalize-string": ["postcss-normalize-string@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ=="],
"postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg=="],
"postcss-normalize-unicode": ["postcss-normalize-unicode@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g=="],
"postcss-normalize-url": ["postcss-normalize-url@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ=="],
"postcss-normalize-whitespace": ["postcss-normalize-whitespace@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA=="],
"postcss-ordered-values": ["postcss-ordered-values@7.0.2", "", { "dependencies": { "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw=="],
"postcss-reduce-initial": ["postcss-reduce-initial@7.0.4", "", { "dependencies": { "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q=="],
"postcss-reduce-transforms": ["postcss-reduce-transforms@7.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g=="],
"postcss-reporter": ["postcss-reporter@7.1.0", "", { "dependencies": { "picocolors": "^1.0.0", "thenby": "^1.3.4" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-svgo": ["postcss-svgo@7.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^4.0.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w=="],
"postcss-unique-selectors": ["postcss-unique-selectors@7.0.4", "", { "dependencies": { "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"pretty-hrtime": ["pretty-hrtime@1.0.3", "", {}, "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A=="],
"promptly": ["promptly@2.2.0", "", { "dependencies": { "read": "^1.0.4" } }, "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA=="],
"proxy-agent": ["proxy-agent@6.4.0", "", { "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.2" } }, "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
"read": ["read@1.0.7", "", { "dependencies": { "mute-stream": "~0.0.4" } }, "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"require-in-the-middle": ["require-in-the-middle@5.2.0", "", { "dependencies": { "debug": "^4.1.1", "module-details-from-path": "^1.0.3", "resolve": "^1.22.1" } }, "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
"run-series": ["run-series@1.1.9", "", {}, "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["semver@7.7.2", "", { "bin": "bin/semver.js" }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"sprintf-js": ["sprintf-js@1.1.2", "", {}, "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"stylehacks": ["stylehacks@7.0.6", "", { "dependencies": { "browserslist": "^4.25.1", "postcss-selector-parser": "^7.1.0" }, "peerDependencies": { "postcss": "^8.4.32" } }, "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="],
"systeminformation": ["systeminformation@5.27.7", "", { "os": "!aix", "bin": "lib/cli.js" }, "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg=="],
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
"tailwindcss": ["tailwindcss@4.1.12", "", {}, "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA=="],
"tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
"thenby": ["thenby@1.3.4", "", {}, "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="],
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tslib": ["tslib@1.9.3", "", {}, "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="],
"tv4": ["tv4@1.3.0", "", {}, "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw=="],
"tx2": ["tx2@1.0.5", "", { "dependencies": { "json-stringify-safe": "^5.0.1" } }, "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vizion": ["vizion@2.2.1", "", { "dependencies": { "async": "^2.6.3", "git-node-fs": "^1.0.0", "ini": "^1.3.5", "js-git": "^0.7.8" } }, "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yaml": ["yaml@2.8.1", "", { "bin": "bin.mjs" }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"@pm2/agent/dayjs": ["dayjs@1.8.36", "", {}, "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw=="],
"@pm2/agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"@pm2/agent/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": "bin/semver.js" }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="],
"@pm2/io/async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="],
"@pm2/io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"@pm2/io/eventemitter2": ["eventemitter2@6.4.9", "", {}, "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg=="],
"@pm2/io/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": "bin/semver.js" }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="],
"@pm2/js-api/async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="],
"@pm2/js-api/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"@pm2/js-api/eventemitter2": ["eventemitter2@6.4.9", "", {}, "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"ast-types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"needle/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"pm2-sysmonit/pidusage": ["pidusage@2.0.21", "", { "dependencies": { "safe-buffer": "^5.2.1" } }, "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"tar/mkdirp": ["mkdirp@3.0.1", "", { "bin": "dist/cjs/src/bin.js" }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"vizion/async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="],
"@pm2/agent/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"@pm2/io/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"@pm2/agent/semver/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"@pm2/io/semver/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
}
}

View File

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

View File

@@ -16,6 +16,7 @@ default: &default
username: <%= ENV.fetch("DB_USERNAME") { "root" } %> username: <%= ENV.fetch("DB_USERNAME") { "root" } %>
password: <%= ENV.fetch("DB_PASSWORD") { "root" } %> password: <%= ENV.fetch("DB_PASSWORD") { "root" } %>
host: <%= ENV.fetch("DB_HOST") { "127.0.0.1" } %> host: <%= ENV.fetch("DB_HOST") { "127.0.0.1" } %>
port: <%= ENV.fetch("DB_port") { 3306 } %>
development: development:
<<: *default <<: *default
@@ -27,6 +28,9 @@ development:
test: test:
<<: *default <<: *default
database: aperonight_test database: aperonight_test
# adapter: sqlite3
# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# database: data/test.sqlite3
# As with config/credentials.yml, you never want to store sensitive information, # As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is # like your database password, in your source code. If your source code is

View File

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

View File

@@ -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).

View File

@@ -28,4 +28,109 @@
# enabled: "ON" # enabled: "ON"
en: en:
hello: "Hello world" activerecord:
models:
user: "User"
party: "Party"
ticket: "Ticket"
ticket_type: "Ticket type"
attributes:
user:
email: "Email"
password: "Password"
password_confirmation: "Password confirmation"
remember_me: "Remember me"
party:
name: "Name"
description: "Description"
start_date: "Start date"
end_date: "End date"
location: "Location"
capacity: "Capacity"
ticket:
user: "User"
ticket_type: "Ticket type"
quantity: "Quantity"
price: "Price"
ticket_type:
name: "Name"
description: "Description"
price: "Price"
available_quantity: "Available quantity"
header:
parties: "Parties & Afterworks"
concerts: "Concerts"
profile: "My Profile"
reservations: "My Reservations"
logout: "Logout"
login: "Login"
register: "Register"
devise:
confirmations:
new:
title: "Resend confirmation instructions"
submit: "Resend confirmation instructions"
description: "Enter your email address and we'll send you the confirmation instructions"
passwords:
new:
title: "Forgot your password?"
description: "Enter your email address and we'll send you a link to reset your password"
submit: "Send reset password instructions"
email_placeholder: "Email address"
edit:
title: "Change your password"
description: "Please enter your new password below"
new_password: "New password"
confirm_new_password: "Confirm new password"
current_password: "Current password"
leave_blank: "leave blank if you don't want to change it"
current_password_required: "required to confirm your changes"
submit: "Change my password"
registrations:
new:
title: "Create your account"
or: "Or"
sign_in_link: "sign in to your account"
sign_up: "Sign up"
continue_with: "Or continue with"
minimum_password_length:
one: "1 character minimum"
other: "%{count} characters minimum"
edit:
title: "Edit your account"
subtitle: "Manage your information and preferences"
waiting_confirmation: "Waiting for confirmation for: %{email}"
new_password: "New password"
confirm_new_password: "Confirm new password"
current_password: "Current password"
leave_blank: "leave blank if you don't want to change it"
current_password_required: "required to confirm your changes"
update: "Update"
delete_account: "Delete my account"
unhappy: "Unhappy?"
confirm_delete: "Are you sure?"
back: "Back"
sessions:
new:
title: "Sign in to your account"
or: "Or"
sign_up_link: "create a new account"
email_placeholder: "Email address"
password_placeholder: "Password"
remember_me: "Remember me"
forgot_password: "Forgot your password?"
sign_in: "Sign in"
continue_with: "Or continue with"
shared:
links:
sign_in: "Sign in"
sign_up: "Register"
forgot_password: "Forgot your password?"
confirmation_instructions: "Didn't receive confirmation instructions?"
unlock_instructions: "Didn't receive unlock instructions?"
sign_in_with: "Sign in with %{provider}"
unlocks:
new:
title: "Resend unlock instructions"
submit: "Resend unlock instructions"
description: "Enter your email address and we'll send you the unlock instructions"

123
config/locales/fr.yml Normal file
View File

@@ -0,0 +1,123 @@
fr:
views:
pagination:
first: "&laquo; Premier"
last: "Dernier &raquo;"
previous: "&lsaquo; Précédent"
next: "Suivant &rsaquo;"
truncate: "&hellip;"
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}&nbsp;-&nbsp;%{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"
header:
parties: "Soirées et afterworks"
concerts: "Concerts"
profile: "Mon profil"
reservations: "Mes réservations"
logout: "Déconnexion"
login: "Se connecter"
register: "S'inscrire"
devise:
confirmations:
new:
title: "Renvoyer les instructions de confirmation"
submit: "Renvoyer les instructions de confirmation"
description: "Entrez votre adresse email et nous vous enverrons les instructions de confirmation"
passwords:
new:
title: "Mot de passe oublié ?"
description: "Entrez votre adresse email et nous vous enverrons un lien pour réinitialiser votre mot de passe"
submit: "Envoyer le lien de réinitialisation"
email_placeholder: "Adresse email"
edit:
title: "Changer votre mot de passe"
description: "Veuillez entrer votre nouveau mot de passe ci-dessous"
new_password: "Nouveau mot de passe"
confirm_new_password: "Confirmer le nouveau mot de passe"
current_password: "Mot de passe actuel"
leave_blank: "laissez vide si vous ne souhaitez pas le changer"
current_password_required: "requis pour confirmer vos changements"
submit: "Changer mon mot de passe"
registrations:
new:
title: "Créer votre compte"
or: "Ou"
sign_in_link: "connectez-vous à votre compte"
sign_up: "S'inscrire"
continue_with: "Ou continuer avec"
minimum_password_length:
one: "1 caractère minimum"
other: "%{count} caractères minimum"
edit:
title: "Modifier votre compte"
subtitle: "Gérez vos informations et préférences"
waiting_confirmation: "En attente de confirmation pour : %{email}"
new_password: "Nouveau mot de passe"
confirm_new_password: "Confirmer le nouveau mot de passe"
current_password: "Mot de passe actuel"
leave_blank: "laissez vide si vous ne souhaitez pas le changer"
current_password_required: "requis pour confirmer vos changements"
update: "Mettre à jour"
delete_account: "Supprimer mon compte"
unhappy: "Mécontent ?"
confirm_delete: "Êtes-vous sûr ?"
back: "Retour"
sessions:
new:
title: "Connexion à votre compte"
or: "Ou"
sign_up_link: "créez un nouveau compte"
email_placeholder: "Adresse email"
password_placeholder: "Mot de passe"
remember_me: "Se souvenir de moi"
forgot_password: "Mot de passe oublié ?"
sign_in: "Se connecter"
continue_with: "Ou continuer avec"
shared:
links:
sign_in: "Se connecter"
sign_up: "S'inscrire"
forgot_password: "Mot de passe oublié ?"
confirmation_instructions: "Vous n'avez pas reçu les instructions de confirmation ?"
unlock_instructions: "Vous n'avez pas reçu les instructions de déverrouillage ?"
sign_in_with: "Se connecter avec %{provider}"
unlocks:
new:
title: "Renvoyer les instructions de déverrouillage"
submit: "Renvoyer les instructions de déverrouillage"
description: "Entrez votre adresse email et nous vous enverrons les instructions de déverrouillage"

View File

@@ -12,17 +12,24 @@ Rails.application.routes.draw do
# Defines the root path route ("/") # Defines the root path route ("/")
root "pages#home" root "pages#home"
# 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"
# 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
@@ -36,10 +43,11 @@ Rails.application.routes.draw do
namespace :v1 do namespace :v1 do
# RESTful routes for party management # RESTful routes for party management
resources :parties, only: [ :index, :show, :create, :update, :destroy ] resources :parties, only: [ :index, :show, :create, :update, :destroy ]
# resources :bundles, only: [ :index, :show, :create, :update, :destroy ]
# Additional API endpoints can be added here as needed # Additional API endpoints can be added here as needed
# Example: search, filtering, user-specific endpoints # Example: search, filtering, user-specific endpoints
end end
end end
end end

View File

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

View File

@@ -2,18 +2,24 @@ class CreateParties < ActiveRecord::Migration[8.0]
def change def change
create_table :parties do |t| create_table :parties do |t|
t.string :name, null: false t.string :name, null: false
t.string :slug, null: false
t.string :image, null: true
t.text :description, null: false t.text :description, null: false
t.integer :state, default: 0, null: false t.integer :state, default: 0, null: false
t.string :venue_name, null: false t.string :venue_name, null: false
t.string :venue_address, null: false t.string :venue_address, null: false
t.datetime :start_time
t.datetime :end_time
t.decimal :latitude, precision: 10, scale: 6, null: false t.decimal :latitude, precision: 10, scale: 6, null: false
t.decimal :longitude, precision: 10, scale: 6, null: false t.decimal :longitude, precision: 10, scale: 6, null: false
t.boolean :featured, default: false, null: false t.boolean :featured, default: false, null: false
t.references :user, null: false, foreign_key: false
t.timestamps t.timestamps
end end
add_index :parties, :state add_index :parties, :state
add_index :parties, :featured add_index :parties, :featured
add_index :parties, [:latitude, :longitude] add_index :parties, [ :latitude, :longitude ]
end end
end end

View File

@@ -1,7 +1,6 @@
class CreateTicketTypes < ActiveRecord::Migration[8.0] class CreateTicketTypes < ActiveRecord::Migration[8.0]
def change def change
create_table :ticket_types do |t| create_table :ticket_types do |t|
t.references :party, null: false, foreign_key: true
t.string :name t.string :name
t.text :description t.text :description
t.integer :price_cents t.integer :price_cents
@@ -10,8 +9,13 @@ class CreateTicketTypes < ActiveRecord::Migration[8.0]
t.datetime :sale_end_at t.datetime :sale_end_at
t.boolean :requires_id t.boolean :requires_id
t.integer :minimum_age t.integer :minimum_age
t.references :party, null: false, foreign_key: false
t.timestamps t.timestamps
end end
add_index :ticket_types, :party_id unless index_exists?(:ticket_types, :party_id)
add_index :ticket_types, :sale_start_at unless index_exists?(:ticket_types, :sale_start_at)
add_index :ticket_types, :sale_end_at unless index_exists?(:ticket_types, :sale_end_at)
end end
end end

View File

@@ -2,8 +2,17 @@ class CreateTickets < ActiveRecord::Migration[8.0]
def change def change
create_table :tickets do |t| create_table :tickets do |t|
t.string :qr_code t.string :qr_code
t.integer :price_cents
t.string :status, default: "active"
t.references :user, null: false, foreign_key: false
t.references :ticket_type, null: false, foreign_key: false
t.timestamps t.timestamps
end end
add_index :tickets, :qr_code, unique: true
add_index :tickets, :user_id unless index_exists?(:tickets, :user_id)
add_index :tickets, :ticket_type_id unless index_exists?(:tickets, :ticket_type_id)
end end
end end

21
db/schema.rb generated
View File

@@ -13,22 +13,27 @@
ActiveRecord::Schema[8.0].define(version: 2025_08_23_171354) do ActiveRecord::Schema[8.0].define(version: 2025_08_23_171354) do
create_table "parties", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "parties", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "slug", null: false
t.string "image"
t.text "description", null: false t.text "description", null: false
t.integer "state", default: 0, null: false t.integer "state", default: 0, null: false
t.string "venue_name", null: false t.string "venue_name", null: false
t.string "venue_address", null: false t.string "venue_address", null: false
t.datetime "start_time"
t.datetime "end_time"
t.decimal "latitude", precision: 10, scale: 6, null: false t.decimal "latitude", precision: 10, scale: 6, null: false
t.decimal "longitude", precision: 10, scale: 6, null: false t.decimal "longitude", precision: 10, scale: 6, null: false
t.boolean "featured", default: false, null: false t.boolean "featured", default: false, null: false
t.bigint "user_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["featured"], name: "index_parties_on_featured" t.index ["featured"], name: "index_parties_on_featured"
t.index ["latitude", "longitude"], name: "index_parties_on_latitude_and_longitude" t.index ["latitude", "longitude"], name: "index_parties_on_latitude_and_longitude"
t.index ["state"], name: "index_parties_on_state" t.index ["state"], name: "index_parties_on_state"
t.index ["user_id"], name: "index_parties_on_user_id"
end end
create_table "ticket_types", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "ticket_types", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
t.bigint "party_id", null: false
t.string "name" t.string "name"
t.text "description" t.text "description"
t.integer "price_cents" t.integer "price_cents"
@@ -37,15 +42,25 @@ ActiveRecord::Schema[8.0].define(version: 2025_08_23_171354) do
t.datetime "sale_end_at" t.datetime "sale_end_at"
t.boolean "requires_id" t.boolean "requires_id"
t.integer "minimum_age" t.integer "minimum_age"
t.bigint "party_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["party_id"], name: "index_ticket_types_on_party_id" t.index ["party_id"], name: "index_ticket_types_on_party_id"
t.index ["sale_end_at"], name: "index_ticket_types_on_sale_end_at"
t.index ["sale_start_at"], name: "index_ticket_types_on_sale_start_at"
end end
create_table "tickets", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "tickets", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
t.string "qr_code" t.string "qr_code"
t.integer "price_cents"
t.string "status", default: "active"
t.bigint "user_id", null: false
t.bigint "ticket_type_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["qr_code"], name: "index_tickets_on_qr_code", unique: true
t.index ["ticket_type_id"], name: "index_tickets_on_ticket_type_id"
t.index ["user_id"], name: "index_tickets_on_user_id"
end end
create_table "users", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| create_table "users", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
@@ -54,11 +69,11 @@ 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
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end end
add_foreign_key "ticket_types", "parties"
end end

View File

@@ -7,3 +7,110 @@
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
# MovieGenre.find_or_create_by!(name: genre_name) # MovieGenre.find_or_create_by!(name: genre_name)
# end # end
# Create admin user for development
admin_user = User.find_or_create_by!(email: 'admin@example.com') do |u|
u.password = 'password'
u.password_confirmation = 'password'
u.last_name = nil
u.first_name = nil
end
# Create regular users for development
users = User.where.not(email: 'admin@example.com').limit(5)
missing_users_count = 5 - users.count
missing_users_count.times do |i|
User.find_or_create_by!(email: "user#{i + 1}@example.com") do |u|
u.password = 'password'
u.password_confirmation = 'password'
u.last_name = nil
u.first_name = nil
end
end
# Reload all users after creation
users = User.all.to_a
# Create sample parties
parties_data = [
{
name: "Summer Beach Party",
slug: "summer-beach-party",
description: "Join us for an amazing night at the beach with music, dancing, and cocktails.",
venue_name: "Sunset Beach Resort",
venue_address: "123 Ocean Drive, Miami, FL",
latitude: 25.7617,
longitude: -80.1918,
start_time: 1.day.from_now,
end_time: 1.day.from_now + 6.hours,
featured: true,
image: "https://fastly.picsum.photos/id/407/300/200.jpg?hmac=9EhoXMZ1QdwJue90vzxcjBg2YzsZsAWCjJ7oxOhtcU0",
user: users.first
},
{
name: "Rooftop Jazz Night",
slug: "rooftop-jazz-night",
description: "Experience smooth jazz under the stars at our exclusive rooftop venue.",
venue_name: "Skyline Rooftop Bar",
venue_address: "456 Downtown Ave, New York, NY",
latitude: 40.7128,
longitude: -74.0060,
start_time: 3.days.from_now,
end_time: 3.days.from_now + 4.hours,
featured: true,
image: "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
user: users.second
},
{
name: "Warehouse Electronic Festival",
slug: "warehouse-electronic-festival",
description: "A night of electronic music and dancing in an industrial warehouse setting.",
venue_name: "Downtown Warehouse",
venue_address: "789 Industrial Blvd, Los Angeles, CA",
latitude: 34.0522,
longitude: -118.2437,
start_time: 1.week.from_now,
end_time: 1.week.from_now + 8.hours,
featured: false,
image: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
user: users.third
}
]
parties = []
parties_data.each do |party_data|
user = party_data.delete(:user)
party = Party.find_or_create_by!(name: party_data[:name]) do |p|
p.assign_attributes(party_data)
p.user = user
p.state = :published
end
parties << party
end
# Create ticket types for each party
parties.each_with_index do |party, index|
# General Admission ticket type
TicketType.find_or_create_by!(party: party, name: "General Admission") do |tt|
tt.description = "General admission ticket for #{party.name}"
tt.price_cents = 2500 # $25.00
tt.quantity = 100
tt.sale_start_at = 1.month.ago
tt.sale_end_at = party.start_time - 1.hour
tt.requires_id = false
tt.minimum_age = 18
end
# VIP ticket type
TicketType.find_or_create_by!(party: party, name: "VIP") do |tt|
tt.description = "VIP access ticket for #{party.name} with premium benefits"
tt.price_cents = 7500 # $75.00
tt.quantity = 20
tt.sale_start_at = 1.month.ago
tt.sale_end_at = party.start_time - 1.hour
tt.requires_id = true
tt.minimum_age = 21
end
end
puts "Created #{User.count} users, #{Party.count} parties, and #{TicketType.count} ticket types"

File diff suppressed because it is too large Load Diff

177
npm-install-635.sh Normal file
View File

@@ -0,0 +1,177 @@
#!/bin/sh
# A word about this shell script:
#
# It must work everywhere, including on systems that lack
# a /bin/bash, map 'sh' to ksh, ksh97, bash, ash, or zsh,
# and potentially have either a posix shell or bourne
# shell living at /bin/sh.
#
# See this helpful document on writing portable shell scripts:
# http://www.gnu.org/s/hello/manual/autoconf/Portable-Shell.html
#
# The only shell it won't ever work on is cmd.exe.
if [ "x$0" = "xsh" ]; then
# run as curl | sh
# on some systems, you can just do cat>npm-install.sh
# which is a bit cuter. But on others, &1 is already closed,
# so catting to another script file won't do anything.
# Follow Location: headers, and fail on errors
curl -f -L -s https://www.npmjs.org/install.sh > npm-install-$$.sh
ret=$?
if [ $ret -eq 0 ]; then
(exit 0)
else
rm npm-install-$$.sh
echo "failed to download script" >&2
exit $ret
fi
sh npm-install-$$.sh
ret=$?
rm npm-install-$$.sh
exit $ret
fi
debug=0
npm_config_loglevel="error"
if [ "x$npm_debug" = "x" ]; then
(exit 0)
else
echo "running in debug mode."
echo "note that this requires bash or zsh."
set -o xtrace
set -o pipefail
npm_config_loglevel="verbose"
debug=1
fi
export npm_config_loglevel
# make sure that node exists
node=`which node 2>&1`
ret=$?
# if not found, try "nodejs" as it is the case on debian
if [ $ret -ne 0 ]; then
node=`which nodejs 2>&1`
ret=$?
fi
if [ $ret -eq 0 ] && [ -x "$node" ]; then
if [ $debug -eq 1 ]; then
echo "found 'node' at: $node"
echo -n "version: "
$node --version
echo ""
fi
(exit 0)
else
echo "npm cannot be installed without node.js." >&2
echo "install node first, and then try again." >&2
echo "" >&2
exit $ret
fi
ret=0
tar="${TAR}"
if [ -z "$tar" ]; then
tar="${npm_config_tar}"
fi
if [ -z "$tar" ]; then
tar=`which tar 2>&1`
ret=$?
fi
if [ $ret -eq 0 ] && [ -x "$tar" ]; then
if [ $debug -eq 1 ]; then
echo "found 'tar' at: $tar"
echo -n "version: "
$tar --version
echo ""
fi
ret=$?
fi
if [ $ret -eq 0 ]; then
(exit 0)
else
echo "this script requires 'tar', please install it and try again."
exit 1
fi
curl=`which curl 2>&1`
ret=$?
if [ $ret -eq 0 ]; then
if [ $debug -eq 1 ]; then
echo "found 'curl' at: $curl"
echo -n "version: "
$curl --version | head -n 1
echo ""
fi
(exit 0)
else
echo "this script requires 'curl', please install it and try again."
exit 1
fi
# set the temp dir
TMP="${TMPDIR}"
if [ "x$TMP" = "x" ]; then
TMP="/tmp"
fi
TMP="${TMP}/npm.$$"
rm -rf "$TMP" || true
mkdir "$TMP"
if [ $? -ne 0 ]; then
echo "failed to mkdir $TMP" >&2
exit 1
fi
BACK="$PWD"
t="${npm_install}"
if [ -z "$t" ]; then
t="latest"
fi
# need to echo "" after, because Posix sed doesn't treat EOF
# as an implied end of line.
url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \
| sed -e 's/^.*tarball":"//' \
| sed -e 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
# try without the -e arg to sed.
url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \
| sed 's/^.*tarball":"//' \
| sed 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
fi
fi
if [ $ret -ne 0 ]; then
echo "failed to get tarball url for npm/$t" >&2
exit $ret
fi
echo "fetching: $url" >&2
cd "$TMP" \
&& curl -SsL -o npm.tgz "$url" \
&& $tar -xzf npm.tgz \
&& cd "$TMP"/package \
&& echo "removing existing npm" \
&& "$node" bin/npm-cli.js rm npm -gf --loglevel=silent \
&& echo "installing npm@$t" \
&& "$node" bin/npm-cli.js install -gf ../npm.tgz \
&& cd "$BACK" \
&& rm -rf "$TMP" \
&& echo "successfully installed npm@$t"
ret=$?
if [ $ret -ne 0 ]; then
echo "failed!" >&2
fi
exit $ret

View File

@@ -5,7 +5,6 @@
"build": "esbuild app/javascript/*.* --bundle --minify --sourcemap=external --format=esm --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx", "build": "esbuild app/javascript/*.* --bundle --minify --sourcemap=external --format=esm --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx",
"build:dev": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx", "build:dev": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx",
"build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css" "build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css"
}, },
"dependencies": { "dependencies": {
"@hotwired/stimulus": "^3.2.2", "@hotwired/stimulus": "^3.2.2",

0
pm2.sh Executable file → Normal file
View File

0
rubocop.sh Executable file → Normal file
View File

0
server.sh Executable file → Normal file
View File

View File

@@ -19,6 +19,37 @@ module.exports = {
}, },
extend: { extend: {
colors: { colors: {
// Custom theme colors from theme-rules.md
purple: {
50: '#faf5ff',
100: '#f3e8ff',
200: '#e9d5ff',
300: '#d8b4fe',
400: '#c084fc',
500: '#a855f7',
600: '#9333ea',
700: '#7e22ce',
800: '#6b21a8',
900: '#581c87',
},
pink: {
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
},
slate: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
},
// Existing shadcn colors
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",

19
test.sh Normal file
View 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

View File

@@ -2,12 +2,13 @@ require "test_helper"
class PagesControllerTest < ActionDispatch::IntegrationTest class PagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do test "should get home" do
get pages_home_url get root_url
assert_response :success assert_response :success
end end
test "should get legals" do # Skip legals test since there's no route for it
get pages_legals_url # test "should get legals" do
assert_response :success # get "/legals"
end # assert_response :success
# end
end end

23
test/fixtures/parties.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: Summer Party
slug: summer-party
description: A great summer party with music and drinks
state: published
venue_name: Beach Club
venue_address: 123 Ocean Drive
latitude: 40.7128
longitude: -74.0060
user: one
two:
name: Winter Gala
slug: winter-gala
description: An elegant winter gala for the holidays
state: draft
venue_name: Grand Hotel
venue_address: 456 Park Avenue
latitude: 40.7589
longitude: -73.9851
user: two

19
test/fixtures/ticket_types.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: General Admission
description: General admission ticket for the event
price_cents: 1000
quantity: 100
sale_start_at: <%= 1.day.ago %>
sale_end_at: <%= 1.day.from_now %>
party: one
two:
name: VIP Access
description: VIP access ticket with special privileges
price_cents: 2500
quantity: 50
sale_start_at: <%= 1.day.ago %>
sale_end_at: <%= 1.day.from_now %>
party: two

View File

@@ -1,7 +1,15 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one: one:
qr_code: MyString qr_code: QR001
user: one
ticket_type: one
price_cents: 1000
status: active
two: two:
qr_code: MyString qr_code: QR002
user: two
ticket_type: two
price_cents: 1500
status: active

View File

@@ -1,11 +1,13 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# This model initially had no columns defined. If you add columns to the one:
# model remove the "{}" from the fixture names and add the columns immediately email: user1@example.com
# below each fixture, per the syntax in the comments below encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
# last_name: Trump
one: {} first_name: Donald
# column: value
# two:
two: {} email: user2@example.com
# column: value encrypted_password: <%= Devise::Encryptor.digest(User, 'password123') %>
last_name: Obama
first_name: Barack

View File

@@ -0,0 +1,14 @@
require "test_helper"
class ApplicationRecordTest < ActiveSupport::TestCase
# Test that ApplicationRecord is abstract
test "should be abstract class" do
assert ApplicationRecord.abstract_class?
end
# Test that ApplicationRecord inherits from ActiveRecord::Base
test "should inherit from ActiveRecord::Base" do
assert_kind_of Class, ApplicationRecord
assert ApplicationRecord < ActiveRecord::Base
end
end

163
test/models/party_test.rb Normal file
View File

@@ -0,0 +1,163 @@
require "test_helper"
class PartyTest < ActiveSupport::TestCase
# Test that Party model exists
test "should be a class" do
assert_kind_of Class, Party
end
# Test validations
test "should not save party without name" do
party = Party.new(description: "Test party description")
assert_not party.save
end
test "should not save party without description" do
party = Party.new(name: "Test Party")
assert_not party.save
end
test "should not save party with name less than 3 characters" do
party = Party.new(name: "AB", description: "Valid description for the party")
assert_not party.save
end
test "should not save party with description less than 10 characters" do
party = Party.new(name: "Valid Party Name", description: "Too short")
assert_not party.save
end
test "should not save party without latitude" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
longitude: 2.3522
)
assert_not party.save
end
test "should not save party without longitude" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 48.8566
)
assert_not party.save
end
test "should not save party with invalid latitude" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 95.0,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street"
)
assert_not party.save
end
test "should not save party with invalid longitude" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 190.0,
venue_name: "Test Venue",
venue_address: "123 Test Street"
)
assert_not party.save
end
test "should not save party without slug" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street"
)
assert_not party.save
end
test "should not save party with slug less than 3 characters" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
slug: "ab"
)
assert_not party.save
end
test "should save valid party" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.new(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user,
)
assert party.save
end
# Test enum states
test "should have valid states" do
assert_equal %w[draft published canceled sold_out], Party.states.keys
end
test "should default to draft state" do
party = Party.new(
name: "Valid Party Name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street"
)
assert_equal "draft", party.state
end
# Test associations
test "should belong to user" do
association = Party.reflect_on_association(:user)
assert_equal :belongs_to, association.macro
end
test "should have many ticket_types" do
association = Party.reflect_on_association(:ticket_types)
assert_equal :has_many, association.macro
end
test "should have many tickets through ticket_types" do
association = Party.reflect_on_association(:tickets)
assert_equal :has_many, association.macro
assert_equal :ticket_types, association.options[:through]
end
# Test scopes
test "should respond to featured scope" do
assert_respond_to Party, :featured
end
test "should respond to published scope" do
assert_respond_to Party, :published
end
test "should respond to search_by_name scope" do
assert_respond_to Party, :search_by_name
end
end

View File

@@ -1,7 +1,257 @@
require "test_helper" require "test_helper"
class TicketTest < ActiveSupport::TestCase class TicketTest < ActiveSupport::TestCase
# test "the truth" do # Test that Ticket model exists
# assert true test "should be a class" do
# end assert_kind_of Class, Ticket
end
# Test validations
test "should not save ticket without qr_code" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.create!(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
ticket = Ticket.new(user: user, ticket_type: ticket_type)
assert_not ticket.save
end
test "should not save ticket with duplicate qr_code" do
# Create first ticket
ticket1 = Ticket.new(qr_code: "unique_qr_code_123")
ticket1.save
# Try to create second ticket with same QR code
ticket2 = Ticket.new(qr_code: "unique_qr_code_123")
assert_not ticket2.save
end
test "should not save ticket without user_id" do
ticket = Ticket.new(qr_code: "unique_qr_code_123")
assert_not ticket.save
end
test "should not save ticket without ticket_type_id" do
ticket = Ticket.new(qr_code: "unique_qr_code_123", user_id: 1)
assert_not ticket.save
end
test "should not save ticket without price_cents" do
ticket = Ticket.new(qr_code: "unique_qr_code_123", user_id: 1, ticket_type_id: 1)
assert_not ticket.save
end
test "should not save ticket with invalid status" do
ticket = Ticket.new(
qr_code: "unique_qr_code_123",
user_id: 1,
ticket_type_id: 1,
price_cents: 1000,
status: "invalid_status"
)
assert_not ticket.save
end
# Test associations
test "should belong to user" do
association = Ticket.reflect_on_association(:user)
assert_equal :belongs_to, association.macro
end
test "should belong to ticket_type" do
association = Ticket.reflect_on_association(:ticket_type)
assert_equal :belongs_to, association.macro
end
test "should have one party through ticket_type" do
association = Ticket.reflect_on_association(:party)
assert_equal :has_one, association.macro
assert_equal :ticket_type, association.options[:through]
end
# Test callbacks
test "should set price from ticket_type on create" do
# This test would require setting up proper fixtures or creating associated records
# which is beyond the scope of basic model testing without a full test environment
assert true # Placeholder until we can set up proper testing environment
end
# Test valid statuses
test "should save ticket with valid active status" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.create!(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
ticket = Ticket.new(
qr_code: "unique_qr_code_123",
user: user,
ticket_type: ticket_type,
status: "active"
)
# The price_cents should be set automatically by the callback
assert ticket.save
end
test "should save ticket with valid used status" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.create!(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
ticket = Ticket.new(
qr_code: "unique_qr_code_456",
user: user,
ticket_type: ticket_type,
status: "used"
)
assert ticket.save
end
test "should save ticket with valid expired status" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.create!(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
ticket = Ticket.new(
qr_code: "unique_qr_code_789",
user: user,
ticket_type: ticket_type,
status: "expired"
)
assert ticket.save
end
test "should save ticket with valid refunded status" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.create!(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
ticket = Ticket.new(
qr_code: "unique_qr_code_999",
user: user,
ticket_type: ticket_type,
status: "refunded"
)
assert ticket.save
end
end end

View File

@@ -0,0 +1,247 @@
require "test_helper"
class TicketTypeTest < ActiveSupport::TestCase
# Test that TicketType model exists
test "should be a class" do
assert_kind_of Class, TicketType
end
# Test validations
test "should not save ticket_type without name" do
ticket_type = TicketType.new(description: "Test ticket type description", price_cents: 1000, quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type without description" do
ticket_type = TicketType.new(name: "VIP Ticket", price_cents: 1000, quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type with name less than 3 characters" do
ticket_type = TicketType.new(name: "AB", description: "Valid description for the ticket type", price_cents: 1000, quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type with description less than 10 characters" do
ticket_type = TicketType.new(name: "Valid Ticket Type Name", description: "Too short", price_cents: 1000, quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type without price_cents" do
ticket_type = TicketType.new(name: "Valid Ticket Type Name", description: "Valid description for the ticket type that is long enough", quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type with invalid price_cents" do
ticket_type = TicketType.new(name: "Valid Ticket Type Name", description: "Valid description for the ticket type that is long enough", price_cents: 0, quantity: 50)
assert_not ticket_type.save
end
test "should not save ticket_type without quantity" do
ticket_type = TicketType.new(name: "Valid Ticket Type Name", description: "Valid description for the ticket type that is long enough", price_cents: 1000)
assert_not ticket_type.save
end
test "should not save ticket_type with invalid quantity" do
ticket_type = TicketType.new(name: "Valid Ticket Type Name", description: "Valid description for the ticket type that is long enough", price_cents: 1000, quantity: 0)
assert_not ticket_type.save
end
test "should not save ticket_type without sale_start_at" do
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_end_at: Time.current + 1.day
)
assert_not ticket_type.save
end
test "should not save ticket_type without sale_end_at" do
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current
)
assert_not ticket_type.save
end
test "should not save ticket_type with sale_end_at before sale_start_at" do
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current + 1.day,
sale_end_at: Time.current
)
assert_not ticket_type.save
end
test "should save valid ticket_type" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
assert ticket_type.save
end
# Test associations
test "should belong to party" do
association = TicketType.reflect_on_association(:party)
assert_equal :belongs_to, association.macro
end
test "should have many tickets" do
association = TicketType.reflect_on_association(:tickets)
assert_equal :has_many, association.macro
end
# Test boolean validation
test "should allow requires_id to be true" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: true,
party: party
)
assert ticket_type.save
end
test "should allow requires_id to be false" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
party: party
)
assert ticket_type.save
end
# Test minimum_age validation
test "should allow minimum_age_to_be_nil" do
user = User.create!(
email: "test@example.com",
password: "password123",
password_confirmation: "password123"
)
party = Party.create!(
name: "Valid Party Name",
slug: "valid-party-name",
description: "Valid description for the party that is long enough",
latitude: 48.8566,
longitude: 2.3522,
venue_name: "Test Venue",
venue_address: "123 Test Street",
user: user
)
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
requires_id: false,
minimum_age: nil,
party: party
)
assert ticket_type.save
end
test "should not save ticket_type with invalid minimum_age" do
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
minimum_age: -1
)
assert_not ticket_type.save
end
test "should not save ticket_type with minimum_age greater than 120" do
ticket_type = TicketType.new(
name: "Valid Ticket Type Name",
description: "Valid description for the ticket type that is long enough",
price_cents: 1000,
quantity: 50,
sale_start_at: Time.current,
sale_end_at: Time.current + 1.day,
minimum_age: 150
)
assert_not ticket_type.save
end
end

View File

@@ -1,7 +1,66 @@
require "test_helper" require "test_helper"
class UserTest < ActiveSupport::TestCase class UserTest < ActiveSupport::TestCase
# test "the truth" do # Test that User model exists
# assert true test "should be a class" do
# end assert_kind_of Class, User
end end
# Test Devise modules
test "should include devise modules" do
user = User.new
assert user.respond_to?(:email)
assert user.respond_to?(:encrypted_password)
end
# Test associations
test "should have many parties" do
association = User.reflect_on_association(:parties)
assert_equal :has_many, association.macro
assert_equal :destroy, association.options[:dependent]
end
test "should have many tickets" do
association = User.reflect_on_association(:tickets)
assert_equal :has_many, association.macro
assert_equal :destroy, association.options[:dependent]
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

View File

@@ -1,6 +1,11 @@
ENV["RAILS_ENV"] ||= "test" ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment" require_relative "../config/environment"
require "rails/test_help" require "rails/test_help"
require "minitest/reporters"
Minitest::Reporters.use!
# Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new, color: true)
# Minitest::Reporters.use! [ Minitest::Reporters::SpecReporter.new, Minitest::Reporters::JUnitReporter.new ]
module ActiveSupport module ActiveSupport
class TestCase class TestCase

View File

@@ -319,37 +319,37 @@
"@tailwindcss/oxide-android-arm64@4.1.12": "@tailwindcss/oxide-android-arm64@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz#27920fe61fa2743afe8a8ca296fa640b609d17d5"
integrity sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ== integrity sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==
"@tailwindcss/oxide-darwin-arm64@4.1.12": "@tailwindcss/oxide-darwin-arm64@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz#e8bd4798f26ec1d012bf0683aeb77449f71505cd"
integrity sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw== integrity sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==
"@tailwindcss/oxide-darwin-x64@4.1.12": "@tailwindcss/oxide-darwin-x64@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz#8ddb7e5ddfd9b049ec84a2bda99f2b04a86859f5"
integrity sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg== integrity sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==
"@tailwindcss/oxide-freebsd-x64@4.1.12": "@tailwindcss/oxide-freebsd-x64@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz#da1c0b16b7a5f95a1e400f299a3ec94fb6fd40ac"
integrity sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww== integrity sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12": "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz#34e558aa6e869c6fe9867cb78ed7ba651b9fcaa4"
integrity sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ== integrity sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==
"@tailwindcss/oxide-linux-arm64-gnu@4.1.12": "@tailwindcss/oxide-linux-arm64-gnu@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz#0a00a8146ab6215f81b2d385056c991441bf390e"
integrity sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g== integrity sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==
"@tailwindcss/oxide-linux-arm64-musl@4.1.12": "@tailwindcss/oxide-linux-arm64-musl@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz#b138f494068884ae0d8c343dc1904b22f5e98dc6"
integrity sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA== integrity sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==
"@tailwindcss/oxide-linux-x64-gnu@4.1.12": "@tailwindcss/oxide-linux-x64-gnu@4.1.12":
@@ -364,7 +364,7 @@
"@tailwindcss/oxide-wasm32-wasi@4.1.12": "@tailwindcss/oxide-wasm32-wasi@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz#9fd15a1ebde6076c42c445c5e305c31673ead965"
integrity sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg== integrity sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==
dependencies: dependencies:
"@emnapi/core" "^1.4.5" "@emnapi/core" "^1.4.5"
@@ -376,7 +376,7 @@
"@tailwindcss/oxide-win32-arm64-msvc@4.1.12": "@tailwindcss/oxide-win32-arm64-msvc@4.1.12":
version "4.1.12" version "4.1.12"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz" resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz#938bcc6a82e1120ea4fe2ce94be0a8cdf3ae92c7"
integrity sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg== integrity sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==
"@tailwindcss/oxide-win32-x64-msvc@4.1.12": "@tailwindcss/oxide-win32-x64-msvc@4.1.12":
@@ -1204,32 +1204,32 @@ jsonfile@^6.0.1:
lightningcss-darwin-arm64@1.30.1: lightningcss-darwin-arm64@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz#3d47ce5e221b9567c703950edf2529ca4a3700ae"
integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== integrity sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==
lightningcss-darwin-x64@1.30.1: lightningcss-darwin-x64@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz#e81105d3fd6330860c15fe860f64d39cff5fbd22"
integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== integrity sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==
lightningcss-freebsd-x64@1.30.1: lightningcss-freebsd-x64@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz#a0e732031083ff9d625c5db021d09eb085af8be4"
integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== integrity sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==
lightningcss-linux-arm-gnueabihf@1.30.1: lightningcss-linux-arm-gnueabihf@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz#1f5ecca6095528ddb649f9304ba2560c72474908"
integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== integrity sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==
lightningcss-linux-arm64-gnu@1.30.1: lightningcss-linux-arm64-gnu@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz#eee7799726103bffff1e88993df726f6911ec009"
integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== integrity sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==
lightningcss-linux-arm64-musl@1.30.1: lightningcss-linux-arm64-musl@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz#f2e4b53f42892feeef8f620cbb889f7c064a7dfe"
integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== integrity sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==
lightningcss-linux-x64-gnu@1.30.1: lightningcss-linux-x64-gnu@1.30.1:
@@ -1244,7 +1244,7 @@ lightningcss-linux-x64-musl@1.30.1:
lightningcss-win32-arm64-msvc@1.30.1: lightningcss-win32-arm64-msvc@1.30.1:
version "1.30.1" version "1.30.1"
resolved "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz" resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz#7d8110a19d7c2d22bfdf2f2bb8be68e7d1b69039"
integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== integrity sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==
lightningcss-win32-x64-msvc@1.30.1: lightningcss-win32-x64-msvc@1.30.1: