Skip to content

Commit

Permalink
Merge pull request #65 from Code-the-Dream-School/HER-56-Implement-Sp…
Browse files Browse the repository at this point in the history
…eaker-Response-Flow

HER-56 -Implement Speaker Response Flow [FE]
  • Loading branch information
mhope21 authored Jan 20, 2025
2 parents 94898e7 + 5e5ed7f commit b0e3098
Show file tree
Hide file tree
Showing 21 changed files with 309 additions and 158 deletions.
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: "projectawarend@gmail.com" }

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

0 comments on commit b0e3098

Please sign in to comment.