refactor(events): replace parties concept with events throughout the application
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> This commit refactors the entire application to replace the 'parties' concept with 'events'. All controllers, models, views, and related files have been updated to reflect this change. The parties table has been replaced with an events table, and all related functionality has been updated accordingly.
This commit is contained in:
0
.cursor/rules/design.mdc
Normal file → Executable file
0
.cursor/rules/design.mdc
Normal file → Executable file
0
.dockerignore
Normal file → Executable file
0
.dockerignore
Normal file → Executable file
0
.editorconfig
Normal file → Executable file
0
.editorconfig
Normal file → Executable file
0
.env.example
Normal file → Executable file
0
.env.example
Normal file → Executable file
0
.gitattributes
vendored
Normal file → Executable file
0
.gitattributes
vendored
Normal file → Executable file
0
.github/dependabot.yml
vendored
Normal file → Executable file
0
.github/dependabot.yml
vendored
Normal file → Executable file
0
.github/workflows/ci.yml
vendored
Normal file → Executable file
0
.github/workflows/ci.yml
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.kamal/hooks/docker-setup.sample
Normal file → Executable file
0
.kamal/hooks/docker-setup.sample
Normal file → Executable file
0
.kamal/hooks/post-app-boot.sample
Normal file → Executable file
0
.kamal/hooks/post-app-boot.sample
Normal file → Executable file
0
.kamal/hooks/post-deploy.sample
Normal file → Executable file
0
.kamal/hooks/post-deploy.sample
Normal file → Executable file
0
.kamal/hooks/post-proxy-reboot.sample
Normal file → Executable file
0
.kamal/hooks/post-proxy-reboot.sample
Normal file → Executable file
0
.kamal/hooks/pre-app-boot.sample
Normal file → Executable file
0
.kamal/hooks/pre-app-boot.sample
Normal file → Executable file
0
.kamal/hooks/pre-build.sample
Normal file → Executable file
0
.kamal/hooks/pre-build.sample
Normal file → Executable file
0
.kamal/hooks/pre-connect.sample
Normal file → Executable file
0
.kamal/hooks/pre-connect.sample
Normal file → Executable file
0
.kamal/hooks/pre-deploy.sample
Normal file → Executable file
0
.kamal/hooks/pre-deploy.sample
Normal file → Executable file
0
.kamal/hooks/pre-proxy-reboot.sample
Normal file → Executable file
0
.kamal/hooks/pre-proxy-reboot.sample
Normal file → Executable file
0
.kamal/secrets
Normal file → Executable file
0
.kamal/secrets
Normal file → Executable file
0
.node-version
Normal file → Executable file
0
.node-version
Normal file → Executable file
0
.rubocop.yml
Normal file → Executable file
0
.rubocop.yml
Normal file → Executable file
0
.ruby-version
Normal file → Executable file
0
.ruby-version
Normal file → Executable file
0
.superdesign/design_iterations/default_ui_darkmode.css
Normal file → Executable file
0
.superdesign/design_iterations/default_ui_darkmode.css
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_components.html
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_components.html
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_home_with_finder.html
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_home_with_finder.html
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_theme.css
Normal file → Executable file
0
.superdesign/design_iterations/enhanced_aperonight_theme.css
Normal file → Executable file
0
.superdesign/design_iterations/neo_brutalist_home.html
Normal file → Executable file
0
.superdesign/design_iterations/neo_brutalist_home.html
Normal file → Executable file
0
.superdesign/design_iterations/neo_brutalist_theme.css
Normal file → Executable file
0
.superdesign/design_iterations/neo_brutalist_theme.css
Normal file → Executable file
0
.tool-versions
Normal file → Executable file
0
.tool-versions
Normal file → Executable file
0
.windsurfrules
Normal file → Executable file
0
.windsurfrules
Normal file → Executable file
0
Dockerfile
Normal file → Executable file
0
Dockerfile
Normal file → Executable file
0
Gemfile.lock
Normal file → Executable file
0
Gemfile.lock
Normal file → Executable file
0
Procfile.dev
Normal file → Executable file
0
Procfile.dev
Normal file → Executable file
16
README.md
Normal file → Executable file
16
README.md
Normal file → Executable file
@@ -1,18 +1,18 @@
|
|||||||
# Aperonight - Party Booking Platform
|
# Aperonight - Event Booking Platform
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 🌃 Overview
|
## 🌃 Overview
|
||||||
|
|
||||||
**Aperonight** is a two-sided marketplace connecting party-goers with nightlife promoters in Paris. The platform allows:
|
**Aperonight** is a two-sided marketplace connecting event-goers with nightlife promoters in Paris. The platform allows:
|
||||||
|
|
||||||
- **Customers** to discover/book tickets for upcoming parties
|
- **Customers** to discover/book tickets for upcoming events
|
||||||
- **Promoters** to create/manage events and validate tickets at venue entrances
|
- **Promoters** to create/manage events and validate tickets at venue entrances
|
||||||
|
|
||||||
## 🎯 Key Features
|
## 🎯 Key Features
|
||||||
|
|
||||||
### For Party-Goers
|
### For Event-Goers
|
||||||
✔ Browse upcoming parties with filters (date, location, music genre)
|
✔ Browse upcoming events with filters (date, location, music genre)
|
||||||
✔ Book tickets with multiple bundle options (VIP, group passes, etc.)
|
✔ Book tickets with multiple bundle options (VIP, group passes, etc.)
|
||||||
✔ Secure payment processing (credit cards, Apple/Google Pay)
|
✔ Secure payment processing (credit cards, Apple/Google Pay)
|
||||||
✔ Mobile-friendly e-tickets with QR codes
|
✔ Mobile-friendly e-tickets with QR codes
|
||||||
@@ -52,13 +52,13 @@ erDiagram
|
|||||||
string email
|
string email
|
||||||
string encrypted_password
|
string encrypted_password
|
||||||
}
|
}
|
||||||
PROMOTER ||--o{ PARTY : creates
|
PROMOTER ||--o{ EVENT : creates
|
||||||
PROMOTER {
|
PROMOTER {
|
||||||
integer id
|
integer id
|
||||||
string stripe_account_id
|
string stripe_account_id
|
||||||
}
|
}
|
||||||
PARTY ||--o{ TICKET_TYPE : has
|
EVENT ||--o{ TICKET_TYPE : has
|
||||||
PARTY {
|
EVENT {
|
||||||
integer id
|
integer id
|
||||||
datetime start_time
|
datetime start_time
|
||||||
}
|
}
|
||||||
|
|||||||
0
app/assets/builds/.keep
Normal file → Executable file
0
app/assets/builds/.keep
Normal file → Executable file
0
app/assets/images/.keep
Normal file → Executable file
0
app/assets/images/.keep
Normal file → Executable file
5
app/assets/stylesheets/application.postcss.css
Normal file → Executable file
5
app/assets/stylesheets/application.postcss.css
Normal file → Executable file
@@ -10,7 +10,10 @@
|
|||||||
@import "components/hero";
|
@import "components/hero";
|
||||||
@import "components/flash";
|
@import "components/flash";
|
||||||
@import "components/footer";
|
@import "components/footer";
|
||||||
@import "components/party-finder";
|
@import "components/event-finder";
|
||||||
|
|
||||||
|
/* Import pages */
|
||||||
|
@import "pages/home";
|
||||||
|
|
||||||
/* Base styles */
|
/* Base styles */
|
||||||
body {
|
body {
|
||||||
|
|||||||
4
app/assets/stylesheets/components/party-finder.css → app/assets/stylesheets/components/event-finder.css
Normal file → Executable file
4
app/assets/stylesheets/components/party-finder.css → app/assets/stylesheets/components/event-finder.css
Normal file → Executable file
@@ -1,4 +1,4 @@
|
|||||||
.party-finder {
|
.event-finder {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: var(--radius-2xl);
|
border-radius: var(--radius-2xl);
|
||||||
box-shadow: var(--shadow-2xl);
|
box-shadow: var(--shadow-2xl);
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.party-finder {
|
.event-finder {
|
||||||
margin: var(--space-8) auto;
|
margin: var(--space-8) auto;
|
||||||
padding: var(--space-6);
|
padding: var(--space-6);
|
||||||
}
|
}
|
||||||
0
app/assets/stylesheets/components/flash.css
Normal file → Executable file
0
app/assets/stylesheets/components/flash.css
Normal file → Executable file
0
app/assets/stylesheets/components/footer.css
Normal file → Executable file
0
app/assets/stylesheets/components/footer.css
Normal file → Executable file
0
app/assets/stylesheets/components/hero.css
Normal file → Executable file
0
app/assets/stylesheets/components/hero.css
Normal file → Executable file
171
app/assets/stylesheets/pages/home.css
Executable file
171
app/assets/stylesheets/pages/home.css
Executable file
@@ -0,0 +1,171 @@
|
|||||||
|
/* Updated Featured Events Grid - 3 Cards Side by Side */
|
||||||
|
.featured-events-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-8);
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.featured-events-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.featured-events-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: all var(--duration-slow) var(--ease-out);
|
||||||
|
border: 1px solid var(--color-neutral-200);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-card:hover {
|
||||||
|
transform: translateY(-8px) scale(1.02);
|
||||||
|
box-shadow: var(--shadow-2xl);
|
||||||
|
border-color: var(--color-primary-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform var(--duration-slow) var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-card:hover .featured-event-image {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-content {
|
||||||
|
padding: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-badges {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
color: var(--color-neutral-900);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
color: var(--color-neutral-600);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-description {
|
||||||
|
color: var(--color-neutral-700);
|
||||||
|
margin-bottom: var(--space-6);
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-price {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--color-primary-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.featured-event-image {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-event-content {
|
||||||
|
padding: var(--space-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced animations */
|
||||||
|
.animate-slideInLeft {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
transition: all 0.5s var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slideInLeft.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slideInRight {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
transition: all 0.5s var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slideInRight.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Added missing animation for fadeInUp */
|
||||||
|
.animate-fadeInUp {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: all 0.5s var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fadeInUp.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Feature Stats Styling */
|
||||||
|
.feature-stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: var(--text-2xl);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-primary-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-neutral-600);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
0
app/assets/stylesheets/theme.css
Normal file → Executable file
0
app/assets/stylesheets/theme.css
Normal file → Executable file
83
app/controllers/api/v1/events_controller.rb
Executable file
83
app/controllers/api/v1/events_controller.rb
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
# Contrôleur API pour la gestion des ressources d'événements
|
||||||
|
# Fournit des points de terminaison RESTful pour les opérations CRUD sur le modèle Event
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class EventsController < ApiController
|
||||||
|
# Charge l'évén avant certaines actions pour réduire les duplications
|
||||||
|
before_action :set_event, only: [ :show, :update, :destroy ]
|
||||||
|
|
||||||
|
# GET /api/v1/events
|
||||||
|
# Récupère tous les événements triés par date de création (du plus récent au plus ancien)
|
||||||
|
def index
|
||||||
|
@events = Event.all.order(created_at: :desc)
|
||||||
|
render json: @events, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/events/:id
|
||||||
|
# Récupère un seul événement par son ID
|
||||||
|
# Retourne 404 si l'événement n'est pas trouvé
|
||||||
|
def show
|
||||||
|
render json: @event, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /api/v1/events
|
||||||
|
# Crée un nouvel événement avec les attributs fournis
|
||||||
|
# Retourne 201 Created en cas de succès avec les données de l'événement
|
||||||
|
# Retourne 422 Unprocessable Entity avec les messages d'erreur en cas d'échec
|
||||||
|
def create
|
||||||
|
@event = Event.new(event_params)
|
||||||
|
if @event.save
|
||||||
|
render json: @event, status: :created
|
||||||
|
else
|
||||||
|
render json: { errors: @event.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# PATCH/PUT /api/v1/events/:id
|
||||||
|
# Met à jour un événement existant avec les attributs fournis
|
||||||
|
# Retourne 200 OK avec les données mises à jour en cas de succès
|
||||||
|
# Retourne 422 Unprocessable Entity avec les messages d'erreur en cas d'échec
|
||||||
|
def update
|
||||||
|
if @event.update(event_params)
|
||||||
|
render json: @event, status: :ok
|
||||||
|
else
|
||||||
|
render json: { errors: @event.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /api/v1/events/:id
|
||||||
|
# Supprime définitivement un événement
|
||||||
|
# Retourne 204 No Content en cas de succès
|
||||||
|
def destroy
|
||||||
|
@event.destroy
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Trouve un événement par son ID ou retourne 404 Introuvable
|
||||||
|
# Utilisé comme before_action pour les actions show, update et destroy
|
||||||
|
def set_event
|
||||||
|
@event = Event.find(params[:id])
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
render json: { error: "Événement non trouvé" }, status: :not_found
|
||||||
|
end
|
||||||
|
|
||||||
|
# Paramètres forts pour la création et la mise à jour des événements
|
||||||
|
# Liste blanche des attributs autorisés pour éviter les vulnérabilités de mass assignment
|
||||||
|
def event_params
|
||||||
|
params.require(:event).permit(
|
||||||
|
:name,
|
||||||
|
:description,
|
||||||
|
:state,
|
||||||
|
:venue_name,
|
||||||
|
:venue_address,
|
||||||
|
:latitude,
|
||||||
|
:longitude,
|
||||||
|
:featured
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# API controller for managing party resources
|
|
||||||
# Provides RESTful endpoints for CRUD operations on Party model
|
|
||||||
module Api
|
|
||||||
module V1
|
|
||||||
class PartiesController < ApiController
|
|
||||||
# Load party before specific actions to reduce duplication
|
|
||||||
before_action :set_party, only: [ :show, :update, :destroy ]
|
|
||||||
|
|
||||||
# GET /api/v1/parties
|
|
||||||
# Returns all parties sorted by creation date (newest first)
|
|
||||||
def index
|
|
||||||
@parties = Party.all.order(created_at: :desc)
|
|
||||||
render json: @parties, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /api/v1/parties/:id
|
|
||||||
# Returns a single party by ID
|
|
||||||
# Returns 404 if party is not found
|
|
||||||
def show
|
|
||||||
render json: @party, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
# POST /api/v1/parties
|
|
||||||
# Creates a new party with provided attributes
|
|
||||||
# Returns 201 Created on success with party data
|
|
||||||
# Returns 422 Unprocessable Entity with validation errors on failure
|
|
||||||
def create
|
|
||||||
@party = Party.new(party_params)
|
|
||||||
if @party.save
|
|
||||||
render json: @party, status: :created
|
|
||||||
else
|
|
||||||
render json: { errors: @party.errors.full_messages }, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# PATCH/PUT /api/v1/parties/:id
|
|
||||||
# Updates an existing party with provided attributes
|
|
||||||
# Returns 200 OK with updated party data on success
|
|
||||||
# Returns 422 Unprocessable Entity with validation errors on failure
|
|
||||||
def update
|
|
||||||
if @party.update(party_params)
|
|
||||||
render json: @party, status: :ok
|
|
||||||
else
|
|
||||||
render json: { errors: @party.errors.full_messages }, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# DELETE /api/v1/parties/:id
|
|
||||||
# Permanently deletes a party
|
|
||||||
# Returns 204 No Content on success
|
|
||||||
def destroy
|
|
||||||
@party.destroy
|
|
||||||
head :no_content
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Finds a party by ID or returns 404 Not Found
|
|
||||||
# Used as before_action for show, update, and destroy actions
|
|
||||||
def set_party
|
|
||||||
@party = Party.find(params[:id])
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
render json: { error: "Party not found" }, status: :not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
# Strong parameters for party creation and updates
|
|
||||||
# Whitelists permitted attributes to prevent mass assignment vulnerabilities
|
|
||||||
def party_params
|
|
||||||
params.require(:party).permit(
|
|
||||||
:name,
|
|
||||||
:description,
|
|
||||||
:state,
|
|
||||||
:venue_name,
|
|
||||||
:venue_address,
|
|
||||||
:latitude,
|
|
||||||
:longitude,
|
|
||||||
:featured
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
0
app/controllers/api_controller.rb
Normal file → Executable file
0
app/controllers/api_controller.rb
Normal file → Executable file
0
app/controllers/application_controller.rb
Normal file → Executable file
0
app/controllers/application_controller.rb
Normal file → Executable file
0
app/controllers/authentications/confirmations_controller.rb
Normal file → Executable file
0
app/controllers/authentications/confirmations_controller.rb
Normal file → Executable file
0
app/controllers/authentications/omniauth_callbacks_controller.rb
Normal file → Executable file
0
app/controllers/authentications/omniauth_callbacks_controller.rb
Normal file → Executable file
0
app/controllers/authentications/passwords_controller.rb
Normal file → Executable file
0
app/controllers/authentications/passwords_controller.rb
Normal file → Executable file
0
app/controllers/authentications/registrations_controller.rb
Normal file → Executable file
0
app/controllers/authentications/registrations_controller.rb
Normal file → Executable file
0
app/controllers/authentications/sessions_controller.rb
Normal file → Executable file
0
app/controllers/authentications/sessions_controller.rb
Normal file → Executable file
0
app/controllers/authentications/unlocks_controller.rb
Normal file → Executable file
0
app/controllers/authentications/unlocks_controller.rb
Normal file → Executable file
0
app/controllers/concerns/.keep
Normal file → Executable file
0
app/controllers/concerns/.keep
Normal file → Executable file
20
app/controllers/parties_controller.rb → app/controllers/events_controller.rb
Normal file → Executable file
20
app/controllers/parties_controller.rb → app/controllers/events_controller.rb
Normal file → Executable file
@@ -1,22 +1,22 @@
|
|||||||
class PartiesController < ApplicationController
|
class EventsController < ApplicationController
|
||||||
# Display all events
|
# Display all events
|
||||||
def index
|
def index
|
||||||
@parties = Party.includes(:user).upcoming.page(params[:page]).per(1)
|
@events = Event.includes(:user).upcoming.page(params[:page]).per(1)
|
||||||
# @parties = Party.page(params[:page]).per(12)
|
# @events = Event.page(params[:page]).per(12)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Display desired event
|
# Display desired event
|
||||||
def show
|
def show
|
||||||
@party = Party.find(params[:id])
|
@event = Event.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle checkout process
|
# Handle checkout process
|
||||||
def checkout
|
def checkout
|
||||||
@party = Party.find(params[:id])
|
@event = Event.find(params[:id])
|
||||||
cart_data = JSON.parse(params[:cart] || "{}")
|
cart_data = JSON.parse(params[:cart] || "{}")
|
||||||
|
|
||||||
if cart_data.empty?
|
if cart_data.empty?
|
||||||
redirect_to party_path(@party), alert: "Please select at least one ticket"
|
redirect_to event_path(@event), alert: "Please select at least one ticket"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class PartiesController < ApplicationController
|
|||||||
total_amount = 0
|
total_amount = 0
|
||||||
|
|
||||||
cart_data.each do |ticket_type_id, item|
|
cart_data.each do |ticket_type_id, item|
|
||||||
ticket_type = @party.ticket_types.find_by(id: ticket_type_id)
|
ticket_type = @event.ticket_types.find_by(id: ticket_type_id)
|
||||||
next unless ticket_type
|
next unless ticket_type
|
||||||
|
|
||||||
quantity = item["quantity"].to_i
|
quantity = item["quantity"].to_i
|
||||||
@@ -34,7 +34,7 @@ class PartiesController < ApplicationController
|
|||||||
# Check availability
|
# Check availability
|
||||||
available = ticket_type.quantity - ticket_type.tickets.count
|
available = ticket_type.quantity - ticket_type.tickets.count
|
||||||
if quantity > available
|
if quantity > available
|
||||||
redirect_to party_path(@party), alert: "Not enough tickets available for #{ticket_type.name}"
|
redirect_to event_path(@event), alert: "Not enough tickets available for #{ticket_type.name}"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class PartiesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if order_items.empty?
|
if order_items.empty?
|
||||||
redirect_to party_path(@party), alert: "Invalid order"
|
redirect_to event_path(@event), alert: "Invalid order"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -59,6 +59,6 @@ class PartiesController < ApplicationController
|
|||||||
|
|
||||||
# For now, we'll just redirect with a success message
|
# For now, we'll just redirect with a success message
|
||||||
# In a real app, you'd redirect to a payment page
|
# In a real app, you'd redirect to a payment page
|
||||||
redirect_to party_path(@party), notice: "Order created successfully! Proceeding to payment..."
|
redirect_to event_path(@event), notice: "Order created successfully! Proceeding to payment..."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
20
app/controllers/pages_controller.rb
Normal file → Executable file
20
app/controllers/pages_controller.rb
Normal file → Executable file
@@ -5,10 +5,10 @@ class PagesController < ApplicationController
|
|||||||
# skip_before_action :authenticate_user!, only: [ :home ]
|
# skip_before_action :authenticate_user!, only: [ :home ]
|
||||||
before_action :authenticate_user!, only: [ :dashboard ]
|
before_action :authenticate_user!, only: [ :dashboard ]
|
||||||
|
|
||||||
# Homepage showing featured parties
|
# Homepage showing featured events
|
||||||
def home
|
def home
|
||||||
# @parties = Party.published.featured.limit(3)
|
# @events = Event.published.featured.limit(3)
|
||||||
# @parties = Party.where(state: :published).order(created_at: :desc)
|
# @events = Event.where(state: :published).order(created_at: :desc)
|
||||||
|
|
||||||
if user_signed_in?
|
if user_signed_in?
|
||||||
return redirect_to(dashboard_path)
|
return redirect_to(dashboard_path)
|
||||||
@@ -18,15 +18,15 @@ class PagesController < ApplicationController
|
|||||||
# 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
|
@available_events = Event.published.count
|
||||||
@events_this_week = Party.published.where("start_time BETWEEN ? AND ?", Date.current.beginning_of_week, Date.current.end_of_week).count
|
@events_this_week = Event.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)
|
@today_events = Event.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)
|
@tomorrow_events = Event.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])
|
@other_events = Event.published.upcoming.where.not("DATE(start_time) IN (?)", [Date.current, Date.current + 1]).order(start_time: :asc).page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Events page showing all published parties with pagination
|
# Events page showing all published events with pagination
|
||||||
def events
|
def events
|
||||||
@parties = Party.published.order(created_at: :desc).page(params[:page])
|
@events = Event.published.order(created_at: :desc).page(params[:page])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
0
app/helpers/application_helper.rb
Normal file → Executable file
0
app/helpers/application_helper.rb
Normal file → Executable file
0
app/helpers/flash_messages_helper.rb
Normal file → Executable file
0
app/helpers/flash_messages_helper.rb
Normal file → Executable file
0
app/helpers/pages_helper.rb
Normal file → Executable file
0
app/helpers/pages_helper.rb
Normal file → Executable file
0
app/javascript/application.js
Normal file → Executable file
0
app/javascript/application.js
Normal file → Executable file
0
app/javascript/components/button.jsx
Normal file → Executable file
0
app/javascript/components/button.jsx
Normal file → Executable file
0
app/javascript/controllers/application.js
Normal file → Executable file
0
app/javascript/controllers/application.js
Normal file → Executable file
49
app/javascript/controllers/counter_controller.js
Normal file → Executable file
49
app/javascript/controllers/counter_controller.js
Normal file → Executable file
@@ -1,9 +1,9 @@
|
|||||||
import { Controller } from "@hotwired/stimulus"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static values = {
|
static values = {
|
||||||
target: Number,
|
target: { type: Number, default: 0 },
|
||||||
decimal: Boolean,
|
decimal: { type: Boolean, default: false },
|
||||||
duration: { type: Number, default: 2000 }
|
duration: { type: Number, default: 2000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,35 +27,44 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
animate() {
|
animate() {
|
||||||
const startValue = 0
|
// Find the target element with data-target-value
|
||||||
const startTime = performance.now()
|
const targetElement = this.element.querySelector('.stat-number');
|
||||||
|
if (!targetElement) return;
|
||||||
|
|
||||||
|
// Get the target value
|
||||||
|
this.targetValue = parseInt(targetElement.getAttribute('data-target-value'), 10) || this.targetValue;
|
||||||
|
|
||||||
|
const startValue = 0;
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
const updateCounter = (currentTime) => {
|
const updateCounter = (currentTime) => {
|
||||||
const elapsedTime = currentTime - startTime
|
const elapsedTime = currentTime - startTime;
|
||||||
const progress = Math.min(elapsedTime / this.durationValue, 1)
|
const progress = Math.min(elapsedTime / this.durationValue, 1);
|
||||||
|
|
||||||
// Easing function for smooth animation
|
// Easing function for smooth animation
|
||||||
const easeOutQuart = 1 - Math.pow(1 - progress, 4)
|
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
||||||
|
|
||||||
let currentValue = startValue + (this.targetValue - startValue) * easeOutQuart
|
let currentValue = startValue + (this.targetValue - startValue) * easeOutQuart;
|
||||||
|
|
||||||
if (this.decimalValue && this.targetValue < 10) {
|
if (this.decimalValue && this.targetValue < 10) {
|
||||||
currentValue = currentValue.toFixed(1)
|
currentValue = currentValue.toFixed(1);
|
||||||
} else {
|
} else {
|
||||||
currentValue = Math.floor(currentValue)
|
currentValue = Math.floor(currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.element.textContent = currentValue
|
// Update only the text content of the target element
|
||||||
|
targetElement.textContent = currentValue;
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
requestAnimationFrame(updateCounter)
|
requestAnimationFrame(updateCounter);
|
||||||
} else {
|
} else {
|
||||||
this.element.textContent = this.decimalValue && this.targetValue < 10
|
const finalValue = this.decimalValue && this.targetValue < 10
|
||||||
? this.targetValue.toFixed(1)
|
? this.targetValue.toFixed(1)
|
||||||
: this.targetValue
|
: this.targetValue;
|
||||||
|
targetElement.textContent = finalValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(updateCounter)
|
requestAnimationFrame(updateCounter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
app/javascript/controllers/featured_event_controller.js
Executable file
86
app/javascript/controllers/featured_event_controller.js
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["card"]
|
||||||
|
static classes = ["visible"]
|
||||||
|
static values = {
|
||||||
|
threshold: { type: Number, default: 0.1 },
|
||||||
|
rootMargin: { type: String, default: '0px 0px -50px 0px' },
|
||||||
|
staggerDelay: { type: Number, default: 0.2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log("FeaturedEventController connected")
|
||||||
|
this.setupIntersectionObserver()
|
||||||
|
this.setupStaggeredAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupIntersectionObserver() {
|
||||||
|
const observerOptions = {
|
||||||
|
threshold: this.thresholdValue,
|
||||||
|
rootMargin: this.rootMarginValue
|
||||||
|
}
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('visible')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, observerOptions)
|
||||||
|
|
||||||
|
// Observe all card elements within this controller's scope
|
||||||
|
const elements = this.cardTargets
|
||||||
|
console.log("Card targets:", elements)
|
||||||
|
elements.forEach(el => {
|
||||||
|
this.observer.observe(el)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setupStaggeredAnimations() {
|
||||||
|
console.log("Setting up staggered animations")
|
||||||
|
console.log("Card targets:", this.cardTargets)
|
||||||
|
// Add staggered animation delays to cards
|
||||||
|
this.cardTargets.forEach((card, index) => {
|
||||||
|
card.style.transitionDelay = `${index * this.staggerDelayValue}s`
|
||||||
|
card.classList.remove('visible')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Old code
|
||||||
|
<script>
|
||||||
|
// Add animation classes when elements are in view
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const observerOptions = {
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '0px 0px -50px 0px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, observerOptions);
|
||||||
|
|
||||||
|
// Observe animated elements
|
||||||
|
document.querySelectorAll('.animate-fadeInUp, .animate-slideInLeft, .animate-slideInRight').forEach(el => {
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add staggered animation delays
|
||||||
|
document.querySelectorAll('.featured-event-card').forEach((card, index) => {
|
||||||
|
card.style.transitionDelay = `${index * 0.2}s`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
*/
|
||||||
0
app/javascript/controllers/flash_message_controller.js
Normal file → Executable file
0
app/javascript/controllers/flash_message_controller.js
Normal file → Executable file
7
app/javascript/controllers/index.js
Normal file → Executable file
7
app/javascript/controllers/index.js
Normal file → Executable file
@@ -5,12 +5,15 @@
|
|||||||
import { application } from "./application"
|
import { application } from "./application"
|
||||||
|
|
||||||
import LogoutController from "./logout_controller"
|
import LogoutController from "./logout_controller"
|
||||||
import FlashMessage from "./flash_message_controller"
|
import FlashMessageController from "./flash_message_controller"
|
||||||
import CounterController from "./counter_controller"
|
import CounterController from "./counter_controller"
|
||||||
|
import FeaturedEventController from "./featured_event_controller"
|
||||||
|
|
||||||
import ShadcnTestController from "./shadcn_test_controller"
|
import ShadcnTestController from "./shadcn_test_controller"
|
||||||
|
|
||||||
application.register("logout", LogoutController) // Allow logout using js
|
application.register("logout", LogoutController) // Allow logout using js
|
||||||
application.register("flash-message", FlashMessage) // Dismiss notification after 5 secondes
|
application.register("flash-message", FlashMessageController) // Dismiss notification after 5 secondes
|
||||||
application.register("counter", CounterController) // Simple counter for homepage
|
application.register("counter", CounterController) // Simple counter for homepage
|
||||||
|
application.register("featured-event", FeaturedEventController) // Featured event controller for homepage
|
||||||
|
|
||||||
application.register("shadcn-test", ShadcnTestController) // Test controller for Shadcn
|
application.register("shadcn-test", ShadcnTestController) // Test controller for Shadcn
|
||||||
|
|||||||
0
app/javascript/controllers/logout_controller.js
Normal file → Executable file
0
app/javascript/controllers/logout_controller.js
Normal file → Executable file
0
app/javascript/controllers/shadcn_test_controller.js
Normal file → Executable file
0
app/javascript/controllers/shadcn_test_controller.js
Normal file → Executable file
4
app/javascript/controllers/ticket_cart_controller.js
Normal file → Executable file
4
app/javascript/controllers/ticket_cart_controller.js
Normal file → Executable file
@@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus"
|
|||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["quantity", "cartCount", "cartTotal", "checkoutButton"]
|
static targets = ["quantity", "cartCount", "cartTotal", "checkoutButton"]
|
||||||
static values = { partyId: String }
|
static values = { eventId: String }
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
this.cart = {}
|
this.cart = {}
|
||||||
@@ -78,7 +78,7 @@ export default class extends Controller {
|
|||||||
|
|
||||||
const form = document.createElement('form')
|
const form = document.createElement('form')
|
||||||
form.method = 'POST'
|
form.method = 'POST'
|
||||||
form.action = `/parties/${this.partyIdValue}/checkout`
|
form.action = `/events/${this.eventIdValue}/checkout`
|
||||||
form.style.display = 'none'
|
form.style.display = 'none'
|
||||||
|
|
||||||
// Add CSRF token
|
// Add CSRF token
|
||||||
|
|||||||
0
app/javascript/lib/utils.js
Normal file → Executable file
0
app/javascript/lib/utils.js
Normal file → Executable file
0
app/jobs/application_job.rb
Normal file → Executable file
0
app/jobs/application_job.rb
Normal file → Executable file
0
app/mailers/application_mailer.rb
Normal file → Executable file
0
app/mailers/application_mailer.rb
Normal file → Executable file
0
app/models/application_record.rb
Normal file → Executable file
0
app/models/application_record.rb
Normal file → Executable file
0
app/models/concerns/.keep
Normal file → Executable file
0
app/models/concerns/.keep
Normal file → Executable file
24
app/models/party.rb → app/models/event.rb
Normal file → Executable file
24
app/models/party.rb → app/models/event.rb
Normal file → Executable file
@@ -1,11 +1,11 @@
|
|||||||
# Party model representing nightlife events and parties
|
# Event model representing nightlife events and events
|
||||||
# Manages event details, location data, and publication state
|
# Manages event details, location data, and publication state
|
||||||
class Party < ApplicationRecord
|
class Event < ApplicationRecord
|
||||||
# Define states for party lifecycle management
|
# Define states for Event lifecycle management
|
||||||
# draft: Initial state when party is being created
|
# draft: Initial state when Event is being created
|
||||||
# published: Party is visible to public and can be discovered
|
# published: Event is visible to public and can be discovered
|
||||||
# canceled: Party has been canceled by organizer
|
# canceled: Event has been canceled by organizer
|
||||||
# sold_out: Party has reached capacity and tickets are no longer available
|
# sold_out: Event has reached capacity and tickets are no longer available
|
||||||
enum :state, {
|
enum :state, {
|
||||||
draft: 0,
|
draft: 0,
|
||||||
published: 1,
|
published: 1,
|
||||||
@@ -18,7 +18,7 @@ class Party < ApplicationRecord
|
|||||||
has_many :ticket_types, dependent: :destroy
|
has_many :ticket_types, dependent: :destroy
|
||||||
has_many :tickets, through: :ticket_types
|
has_many :tickets, through: :ticket_types
|
||||||
|
|
||||||
# Validations for party attributes
|
# Validations for Event 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 :slug, presence: true, length: { minimum: 3, maximum: 100 }
|
||||||
@@ -40,12 +40,12 @@ class Party < ApplicationRecord
|
|||||||
less_than_or_equal_to: 180
|
less_than_or_equal_to: 180
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scopes for querying parties with common filters
|
# Scopes for querying events with common filters
|
||||||
scope :featured, -> { where(featured: true) } # Get featured parties for homepage
|
scope :featured, -> { where(featured: true) } # Get featured events for homepage
|
||||||
scope :published, -> { where(state: :published) } # Get publicly visible parties
|
scope :published, -> { where(state: :published) } # Get publicly visible events
|
||||||
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)
|
||||||
|
|
||||||
# Scope for published parties ordered by start time
|
# Scope for published events ordered by start time
|
||||||
scope :upcoming, -> { published.where("start_time >= ?", Time.current).order(start_time: :asc) }
|
scope :upcoming, -> { published.where("start_time >= ?", Time.current).order(start_time: :asc) }
|
||||||
|
|
||||||
end
|
end
|
||||||
2
app/models/ticket.rb
Normal file → Executable file
2
app/models/ticket.rb
Normal file → Executable file
@@ -2,7 +2,7 @@ class Ticket < ApplicationRecord
|
|||||||
# Associations
|
# Associations
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :ticket_type
|
belongs_to :ticket_type
|
||||||
has_one :party, through: :ticket_type
|
has_one :event, through: :ticket_type
|
||||||
|
|
||||||
# Validations
|
# Validations
|
||||||
validates :qr_code, presence: true, uniqueness: true
|
validates :qr_code, presence: true, uniqueness: true
|
||||||
|
|||||||
5
app/models/ticket_type.rb
Normal file → Executable file
5
app/models/ticket_type.rb
Normal file → Executable file
@@ -1,6 +1,6 @@
|
|||||||
class TicketType < ApplicationRecord
|
class TicketType < ApplicationRecord
|
||||||
# Associations
|
# Associations
|
||||||
belongs_to :party
|
belongs_to :event
|
||||||
has_many :tickets, dependent: :destroy
|
has_many :tickets, dependent: :destroy
|
||||||
|
|
||||||
# Validations
|
# Validations
|
||||||
@@ -8,12 +8,13 @@ class TicketType < ApplicationRecord
|
|||||||
validates :description, presence: true, length: { minimum: 10, maximum: 500 }
|
validates :description, presence: true, length: { minimum: 10, maximum: 500 }
|
||||||
validates :price_cents, presence: true, numericality: { greater_than: 0 }
|
validates :price_cents, presence: true, numericality: { greater_than: 0 }
|
||||||
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
||||||
validates :party_id, presence: true
|
|
||||||
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
|
||||||
|
validates :event_id, presence: true
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
2
app/models/user.rb
Normal file → Executable file
2
app/models/user.rb
Normal file → Executable file
@@ -20,7 +20,7 @@ class User < ApplicationRecord
|
|||||||
:recoverable, :rememberable, :validatable
|
:recoverable, :rememberable, :validatable
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
has_many :parties, dependent: :destroy
|
has_many :events, dependent: :destroy
|
||||||
has_many :tickets, dependent: :destroy
|
has_many :tickets, dependent: :destroy
|
||||||
|
|
||||||
# Validations
|
# Validations
|
||||||
|
|||||||
10
app/views/components/_party_finder.html.erb → app/views/components/_event_finder.html.erb
Normal file → Executable file
10
app/views/components/_party_finder.html.erb → app/views/components/_event_finder.html.erb
Normal file → Executable file
@@ -1,10 +1,10 @@
|
|||||||
<!-- Party Finder Section -->
|
<!-- Event Finder Section -->
|
||||||
<section style="padding: 0;">
|
<section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="party-finder animate-fadeInUp">
|
<div class="event-finder">
|
||||||
<div class="finder-header">
|
<div class="finder-header">
|
||||||
<h2 class="finder-title">Find Your Perfect Event</h2>
|
<h2 class="finder-title">Find Your Perfect Event</h2>
|
||||||
<p class="finder-subtitle">Discover afterwork parties tailored to your preferences</p>
|
<p class="finder-subtitle">Discover afterwork events tailored to your preferences</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="finder-form">
|
<form class="finder-form">
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Party Finder Functionality
|
// Event Finder Functionality
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
const priceMin = document.getElementById('price-min');
|
const priceMin = document.getElementById('price-min');
|
||||||
const priceMax = document.getElementById('price-max');
|
const priceMax = document.getElementById('price-max');
|
||||||
8
app/views/components/_party_item.html.erb → app/views/components/_event_item.html.erb
Normal file → Executable file
8
app/views/components/_party_item.html.erb → app/views/components/_event_item.html.erb
Normal file → Executable file
@@ -1,14 +1,14 @@
|
|||||||
<%= 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 %>
|
<%= link_to event_path(event.slug, event), 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="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">
|
<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? %>
|
<%= image_tag event.image, alt: event.name, class: "w-full h-full object-cover" if event.image.present? %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<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">
|
<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 %>
|
<%= event.name %>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||||
<%= l(party.start_time, format: :short) %>
|
<%= l(event.start_time, format: :short) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
0
app/views/components/_footer.html.erb
Normal file → Executable file
0
app/views/components/_footer.html.erb
Normal file → Executable file
0
app/views/components/_header.html.erb
Normal file → Executable file
0
app/views/components/_header.html.erb
Normal file → Executable file
0
app/views/components/_metric_card.html.erb
Normal file → Executable file
0
app/views/components/_metric_card.html.erb
Normal file → Executable file
0
app/views/components/_ticket_card.html.erb
Normal file → Executable file
0
app/views/components/_ticket_card.html.erb
Normal file → Executable file
0
app/views/devise/confirmations/new.html.erb
Normal file → Executable file
0
app/views/devise/confirmations/new.html.erb
Normal file → Executable file
0
app/views/devise/mailer/confirmation_instructions.html.erb
Normal file → Executable file
0
app/views/devise/mailer/confirmation_instructions.html.erb
Normal file → Executable file
0
app/views/devise/mailer/email_changed.html.erb
Normal file → Executable file
0
app/views/devise/mailer/email_changed.html.erb
Normal file → Executable file
0
app/views/devise/mailer/password_change.html.erb
Normal file → Executable file
0
app/views/devise/mailer/password_change.html.erb
Normal file → Executable file
0
app/views/devise/mailer/reset_password_instructions.html.erb
Normal file → Executable file
0
app/views/devise/mailer/reset_password_instructions.html.erb
Normal file → Executable file
0
app/views/devise/mailer/unlock_instructions.html.erb
Normal file → Executable file
0
app/views/devise/mailer/unlock_instructions.html.erb
Normal file → Executable file
0
app/views/devise/passwords/edit.html.erb
Normal file → Executable file
0
app/views/devise/passwords/edit.html.erb
Normal file → Executable file
0
app/views/devise/passwords/new.html.erb
Normal file → Executable file
0
app/views/devise/passwords/new.html.erb
Normal file → Executable file
0
app/views/devise/registrations/edit.html.erb
Normal file → Executable file
0
app/views/devise/registrations/edit.html.erb
Normal file → Executable file
0
app/views/devise/registrations/new.html.erb
Normal file → Executable file
0
app/views/devise/registrations/new.html.erb
Normal file → Executable file
0
app/views/devise/sessions/new.html.erb
Normal file → Executable file
0
app/views/devise/sessions/new.html.erb
Normal file → Executable file
0
app/views/devise/shared/_error_messages.html.erb
Normal file → Executable file
0
app/views/devise/shared/_error_messages.html.erb
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user