Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HER-56 -Implement Speaker Response Flow [FE] #65

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/controllers/api/v1/availabilities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def index
# trigger_recurring_availability_job(viewing_month, viewing_year)

# Fetch the availabilities within the specified date range
@availabilities = Availability.where(start_time: start_date..end_date)
@availabilities = Availability.future.where(start_time: start_date..end_date)
@availabilities = @availabilities.where(speaker_id: speaker_id) if speaker_id.present?
@availabilities = @availabilities.where(booked: false)

Expand All @@ -47,6 +47,12 @@ def create
return render json: { error: "Speaker not found" }, status: :not_found
end

# Check if the start_time is in the past
start_time = availability_params[:start_time]
if start_time.present? && start_time < Time.now
return render json: { error: "Start time must be in the future" }, status: :unprocessable_entity
end

availability_attributes = availability_params.except(:is_recurring, :recurring_end_date)
@availability = @speaker.availabilities.create!(availability_attributes)

Expand Down
70 changes: 48 additions & 22 deletions app/controllers/api/v1/bookings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ def show
def create
@booking = Booking.new(booking_params)
@booking.user = current_user

if @booking.save
availability = Availability.find(@booking.availability_id)
availability.update(booked: true)

BookingMailer.booking_confirmation(@booking.user, @booking).deliver_now
BookingMailer.new_booking_notification(@booking.speaker, @booking).deliver_now

render json: BookingSerializer.new(@booking).serializable_hash, status: :created
else
render json: @booking.errors, status: :unprocessable_entity
Expand All @@ -34,31 +37,18 @@ def update
@booking = Booking.find(params[:id])
@availability = @booking.availability

Rails.logger.info("Updating booking with ID: #{@booking.id}")
Rails.logger.info("Availability ID: #{@availability.id}")
Rails.logger.info("Params: #{params.inspect}")
Rails.logger.info("Booking Times - Start: #{params[:start_time]}, End: #{params[:end_time]}")
Rails.logger.info("Availability Times - Start: #{@availability.start_time}, End: #{@availability.end_time}")


if @booking.status == "pending"
if current_user.role == "teacher"
# Ensure the update is within the original availability times
if params[:start_time] >= @availability.start_time && params[:end_time] <= @availability.end_time
if @booking.update(booking_params)
BookingMailer.booking_modified_notification(@booking.speaker, @booking).deliver_now
render json: BookingSerializer.new(@booking).serializable_hash.to_json
else
render json: @booking.errors, status: :unprocessable_entity
end
else
render json: { error: "New times are not within the original availability times." }, status: :unprocessable_entity
end
handle_teacher_update
elsif current_user.role == "speaker"
# Ensure the status update is valid
if params[:status] && [ "pending", "confirmed", "denied" ].include?(params[:status])
@booking.status = params[:status]
if @booking.save
render json: BookingSerializer.new(@booking).serializable_hash.to_json
else
render json: @booking.errors, status: :unprocessable_entity
end
else
render json: { error: "Invalid status value." }, status: :unprocessable_entity
end
handle_speaker_update
else
render json: { error: "User does not have permission to update." }, status: :unprocessable_entity
end
Expand All @@ -80,6 +70,42 @@ def bookings_by_speaker

private

def handle_teacher_update
if @booking.update(booking_params)
BookingMailer.booking_modified_notification(@booking.speaker, @booking).deliver_now
render json: BookingSerializer.new(@booking).serializable_hash.to_json
else
render json: @booking.errors, status: :unprocessable_entity
end
end

def handle_speaker_update
if valid_status_update?
case params[:status]
when "confirmed"
if @booking.accept!
render json: { message: "Booking accepted successfully.", booking: BookingSerializer.new(@booking).serializable_hash }, status: :ok
else
render json: { error: "Failed to accept booking." }, status: :unprocessable_entity
end
when "denied"
if @booking.denied!
render json: { message: "Booking denied successfully.", booking: BookingSerializer.new(@booking).serializable_hash }, status: :ok
else
render json: { error: "Failed to decline booking." }, status: :unprocessable_entity
end
else
render json: { error: "Unsupported status update." }, status: :unprocessable_entity
end
else
render json: { error: "Invalid status value." }, status: :unprocessable_entity
end
end

def valid_status_update?
params[:status] && %w[pending confirmed denied].include?(params[:status])
end

def set_booking
@booking = Booking.includes(:event).find(params[:id])
end
Expand Down
18 changes: 2 additions & 16 deletions app/controllers/api/v1/orders_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def destroy
def create_booking_order
# Check if address information is provided (either address_id or address_attributes)
unless params[:order][:address_id].present? || params[:order][:address_attributes].present?
return render json: { error: "Address information is required" }, status: :unprocessable_entity
return render json: { error: "Address information is required. Please complete your Organization address in your profile." }, status: :unprocessable_entity
end

booking = Booking.find_by(id: params[:product_id])
Expand All @@ -65,7 +65,6 @@ def create_booking_order
return render json: { error: @order.errors.full_messages }, status: :unprocessable_entity
end

# associate_address_with_user(@order)
render json: { order: @order, message: "Booking order created successfully" }, status: :created
end

Expand Down Expand Up @@ -104,7 +103,7 @@ def create_kit_order
end
Rails.logger.info("Order created successfully: #{@order.inspect}")
Rails.logger.info("Associating address with user if save_to_user is true...")
# associate_address_with_user(@order)
associate_address_with_user(@order)
Rails.logger.info("Address association complete.")
Rails.logger.info("Kit order created successfully: #{@order.inspect}")
render json: {
Expand Down Expand Up @@ -139,19 +138,6 @@ def find_or_create_address(address_attributes)
end
end

# def associate_address_with_user(order)
# return unless order.address && order.user

# existing_address = order.user.addresses.find_by(
# street_address: order.address.street_address,
# city: order.address.city,
# state: order.address.state,
# postal_code: order.address.postal_code
# )

# order.user.addresses << order.address unless existing_address
# end

def associate_address_with_user(order)
if order.address && order.user
# Check if the address should be saved to the user
Expand Down
34 changes: 30 additions & 4 deletions app/mailers/booking_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,46 @@ def booking_confirmation(user, booking)
@user = user
@booking = booking
@order = @booking.order
mail(to: @user.email, subject: "Booking Request Confirmation")
mail(to: @user.email, subject: "Booking Request Confirmation") do |format|
format.text { render plain: "Thank you for your booking request! We have received it and will review it shortly." }
format.html { render html: "<h1>Booking Request Confirmation</h1><p>Thank you for your booking request! We have received it and will review it shortly.</p>".html_safe }
end
end

def new_booking_notification(speaker, booking)
@speaker = speaker
@booking = booking
@order = @booking.order
mail(to: @speaker.email, subject: "New Booking Request")
mail(to: @speaker.email, subject: "New Booking Request") do |format|
format.text { render plain: "You have a new booking request. Please review the details." }
format.html { render html: "<h1>New Booking Request</h1><p>You have a new booking request. Please review the details.</p>".html_safe }
end
end

def booking_modified_notification(speaker, booking)
@speaker = speaker
@booking = booking
@location = @booking.user.organization&.addresses&.first
mail(to: @speaker.email, subject: "Booking Request Modified")
mail(to: @speaker.email, subject: "Booking Request Modified") do |format|
format.text { render plain: "Your booking request has been modified. Please check the updated details." }
format.html { render html: "<h1>Booking Request Modified</h1><p>Your booking request has been modified. Please check the updated details.</p>".html_safe }
end
end

def booking_accepted_notification(user, booking)
@user = user
@booking = booking
mail(to: @user.email, subject: "Booking Request Accepted") do |format|
format.text { render plain: "The booking status is #{@booking.status}. Please visit your profile for details." }
format.html { render html: "<h1>Your booking has been accepted</h1><p>Details: The booking status is #{@booking.status}. Please visit your profile for details.</p>".html_safe }
end
end

def booking_denied_notification(user, booking)
@user = user
@booking = booking
mail(to: @user.email, subject: "Booking Request Denied") do |format|
format.text { render plain: "We regret to inform you that your booking has been declined." }
format.html { render html: "<h1>Your booking has been declined</h1><p>We regret to inform you that your booking has been declined.</p>".html_safe }
end
end
end
1 change: 1 addition & 0 deletions app/models/availability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Availability < ApplicationRecord
# Scope to filter by booked status
scope :booked, -> { where(booked: true) }
scope :available, -> { where(booked: false) }
scope :future, -> { where("start_time >= ?", Time.now) }

# Validations
validates :start_time, :end_time, :speaker, presence: true
Expand Down
10 changes: 10 additions & 0 deletions app/models/booking.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ def availability_window
"#{availability.start_time} - #{availability.end_time}" if availability.present?
end

def accept!
update!(status: "confirmed")
BookingMailer.booking_accepted_notification(user, self).deliver_now
end

def denied!
update!(status: "denied")
BookingMailer.booking_denied_notification(user, self).deliver_now
end


validates :start_time, presence: true
validates :end_time, presence: true
Expand Down
20 changes: 19 additions & 1 deletion app/serializers/booking_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class BookingSerializer
include JSONAPI::Serializer
attributes :id, :event_name, :event_speaker, :start_time, :end_time, :status, :event_id, :availability_id, :availability_window
attributes :id, :event_name, :event_speaker, :start_time, :end_time, :status, :event_id, :availability_id, :availability_window, :booking_name, :booking_location

belongs_to :event, serializer: EventSerializer

Expand All @@ -14,6 +14,24 @@ class BookingSerializer
booking.availability_window
end

attribute :booking_name do |booking|
# Ensure booking.order and user are present before accessing
if booking.order && booking.order.user.present?
booking.order.user.name
else
nil
end
end

attribute :booking_location do |booking|
# Ensure booking.order, user, and organization are present before accessing
if booking.order && booking.order.user && booking.order.user.organization.present?
booking.order.user.organization.addresses
else
nil
end
end

def start_time
object.start_time.iso8601
end
Expand Down
4 changes: 4 additions & 0 deletions app/serializers/user_profile_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class UserProfileSerializer
end
end

attribute :addresses do |user|
user.addresses.map { |address| AddressSerializer.new(address).serializable_hash[:data][:attributes] }
end

attribute :organization do |user|
if user.organization
OrganizationSerializer.new(user.organization).serializable_hash[:data][:attributes]
Expand Down
6 changes: 3 additions & 3 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@
config.active_storage.service = :local

# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.raise_delivery_errors = true

# Disable caching for Action Mailer templates even if Action Controller
# caching is enabled.
config.action_mailer.perform_caching = false
# I set up an email for the project, needed for sending password reset email?
config.action_mailer.delivery_method = :sendmail
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.default_options = { from: "[email protected]" }

Expand All @@ -47,7 +47,7 @@
# config.action_mailer.smtp_settings = {
# address: "smtp.gmail.com",
# port: 587,
# domain: "gmail.com",
# domain: "example.com",
# user_name: Rails.application.credentials.gmail[:email],
# password: Rails.application.credentials.gmail[:password],
# authentication: "plain",
Expand Down
21 changes: 10 additions & 11 deletions frontend/src/components/AvailabilityModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const AvailabilityModal = ({ speakerId, isOpen, onClose, selectedDate, setEvents
setIsRecurring(availability.is_recurring);
setRecurringEndDate(availability.recurring_end_date ? new Date (availability.recurring_end_date) : null);
} else {
const localStartTime = new Date(selectedDate).toLocaleString();setStartTime(new Date(localStartTime));
setEndTime(new Date(localStartTime));
setStartTime(new Date(selectedDate));
setEndTime(new Date(selectedDate));
}
}, [availability, selectedDate]);

Expand All @@ -30,13 +30,18 @@ const AvailabilityModal = ({ speakerId, isOpen, onClose, selectedDate, setEvents
return;
}

if (isRecurring && !recurringEndDate) {
alert("Please select a recurring end date.");
return;
}

const newAvailability = {
availability: {
speaker_id: speakerId,
start_time: startTime.toISOString(),
end_time: endTime.toISOString(),
is_recurring: isRecurring,
recurring_end_date: isRecurring ? recurringEndDate : null,
recurring_end_date: isRecurring ? recurringEndDate.toISOString() : null,
}
};

Expand All @@ -53,7 +58,7 @@ const AvailabilityModal = ({ speakerId, isOpen, onClose, selectedDate, setEvents

if (response.ok) {
const data = await response.json();

console.log("Availability data: ", data)
const newEvent = {
id: data.id,
start: data.start_time,
Expand Down Expand Up @@ -87,7 +92,7 @@ const AvailabilityModal = ({ speakerId, isOpen, onClose, selectedDate, setEvents
alert("Your availability has been created.")
} else {
const errorData = await response.json();
setErrorMessage(errorData.errors.join(', '));
setErrorMessage(errorData.error);
}
} catch (error) {
console.error('Error creating availability:', error);
Expand All @@ -106,12 +111,6 @@ const AvailabilityModal = ({ speakerId, isOpen, onClose, selectedDate, setEvents
onClose();
}

useEffect(() => {
const localStartTime = new Date(selectedDate).toLocaleString(); // Convert to local time
setStartTime(new Date(localStartTime));
setEndTime(new Date(localStartTime));
}, [selectedDate]);

return (
<Modal show={isOpen} onHide={onClose}>
<Modal.Header closeButton>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/BookingModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ console.log("Booking ID: ", booking.data.id);
zIndex: 1000,
},
content: {
width: "400px",
width: "600px",
margin: "auto",
padding: "20px",
borderRadius: "8px",
Expand Down
Loading
Loading