Added users support

This commit is contained in:
2025-08-19 07:24:18 +02:00
parent 541b181c87
commit 20b62e7312
18 changed files with 592 additions and 35 deletions

View File

@@ -1,5 +1,6 @@
class ApplicationController < ActionController::Base
before_action :set_locale
before_action :require_login
private
@@ -8,27 +9,37 @@ class ApplicationController < ActionController::Base
session[:locale] = I18n.locale
end
# Optional: Make locale persist across requests via URL helpers
def default_url_options
{ locale: I18n.locale }
end
def set_company
# This should be handled by your authentication system
# But for now, we'll use a placeholder
company_id = session[:company_id]
unless company_id && Company.exists?(company_id)
# If no company in session or it doesn't exist, use the first company
company_id = Company.first&.id
session[:company_id] = company_id
end
@company = Company.find(company_id) if company_id
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
!!current_user
end
def require_login
return if logged_in?
flash[:alert] = t('sessions.login_required')
redirect_to login_path
end
def set_company
return unless logged_in?
@company = current_user.company
return if @company
redirect_to companies_path, alert: 'No company found. Please create a company first.'
end
def current_company
@company
end
helper_method :current_company
helper_method :current_user, :logged_in?, :current_company
end

View File

@@ -219,5 +219,10 @@ class ReservationsController < ApplicationController
end
@company = Company.includes(:teams).find(company_id) if company_id
unless @company
redirect_to companies_path, alert: 'No company found. Please create a company first.'
return
end
end
end

View File

@@ -0,0 +1,25 @@
class SessionsController < ApplicationController
skip_before_action :require_login, only: %i[new create]
def new
end
def create
user = User.find_by_login(params[:login])
if user&.authenticate(params[:password])
session[:user_id] = user.id
session[:company_id] = user.company_id
redirect_to root_path, notice: t('sessions.login_successful')
else
flash.now[:alert] = t('sessions.invalid_credentials')
render :new, status: :unprocessable_entity
end
end
def destroy
session[:user_id] = nil
session[:company_id] = nil
redirect_to login_path, notice: t('sessions.logout_successful')
end
end

View File

@@ -2,4 +2,5 @@ class Company < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :reservations, dependent: :destroy
has_many :teams, dependent: :destroy
has_many :users, dependent: :destroy
end

23
app/models/user.rb Normal file
View File

@@ -0,0 +1,23 @@
class User < ApplicationRecord
has_secure_password
belongs_to :company
validates :username, presence: true, uniqueness: { case_sensitive: false }
validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 6 }, if: -> { new_record? || password.present? }
validates :company_id, presence: true
before_save :downcase_username_and_email
def self.find_by_login(login)
find_by(username: login.downcase) || find_by(email: login.downcase)
end
private
def downcase_username_and_email
self.username = username.downcase.strip if username.present?
self.email = email.downcase.strip if email.present?
end
end

View File

@@ -18,6 +18,15 @@
</head>
<body>
<% if logged_in? %>
<div class="fixed top-4 right-4 text-sm text-gray-600 bg-white px-3 py-1 rounded-md shadow-sm border">
<span class="lowercase"><%= current_user.username %></span>
<span class="mx-2">|</span>
<%= link_to t('sessions.logout_button'), logout_path, method: :delete,
class: "text-blue-600 hover:text-blue-800" %>
</div>
<% end %>
<main class="container mx-auto mt-28 px-5 flex">
<%= yield %>
</main>

View File

@@ -62,7 +62,7 @@
<div class="my-5">
<%= form.label :team_id, t('reservations.form.team') %>
<%= form.collection_select :team_id,
@company.teams,
@company&.teams || [],
:id,
:name,
{ prompt: t('reservations.form.select_team') },

View File

@@ -28,10 +28,12 @@
<label for="team-filter" class="mr-2 font-medium"><%= t('.filter_by_team') %>:</label>
<select id="team-filter" data-main-calendar-target="teamFilter" data-action="change->main-calendar#filterByTeam" class="rounded-md border-gray-300 shadow-sm px-3 py-1 bg-white">
<option value="all"><%= t('.all_teams') %></option>
<% @company.teams.each do |team| %>
<option value="<%= team.id %>" style="background-color: <%= team_color(team.id) %>; color: #000000; padding-left: 10px;">
<%= team.name %>
</option>
<% if @company&.teams %>
<% @company.teams.each do |team| %>
<option value="<%= team.id %>" style="background-color: <%= team_color(team.id) %>; color: #000000; padding-left: 10px;">
<%= team.name %>
</option>
<% end %>
<% end %>
</select>
</div>

View File

@@ -0,0 +1,24 @@
<div class="max-w-md mx-auto mt-8 bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold text-center mb-6"><%= t('sessions.login') %></h2>
<%= form_with url: login_path, method: :post, local: true, class: "space-y-4" do |form| %>
<div>
<%= form.label :login, t('sessions.username_or_email'), class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.text_field :login, required: true,
class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
placeholder: t('sessions.username_or_email_placeholder') %>
</div>
<div>
<%= form.label :password, t('sessions.password'), class: "block text-sm font-medium text-gray-700 mb-1" %>
<%= form.password_field :password, required: true,
class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
placeholder: t('sessions.password_placeholder') %>
</div>
<div>
<%= form.submit t('sessions.login_button'),
class: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200" %>
</div>
<% end %>
</div>