# Story 11.3: Guest Email Notifications & Admin Integration ## Epic Reference **Epic 11:** Guest Booking ## Story Context This story completes the guest booking workflow by implementing email notifications for guests and updating the admin interface to properly display and manage guest bookings alongside client bookings. ## User Story As a **guest** who submitted a booking, I want **to receive email confirmations about my booking status**, So that **I know my request was received and can track its progress**. As an **admin**, I want **to see guest contact information when reviewing bookings**, So that **I can contact them and manage their appointments**. ## Acceptance Criteria ### Guest Email Notifications - [ ] Guest receives confirmation email when booking submitted - [ ] Guest receives approval email when booking approved (with date/time details) - [ ] Guest receives rejection email when booking rejected - [ ] All emails use existing email template/branding - [ ] Emails sent to guest_email address - [ ] Bilingual support based on site locale at submission time ### Admin Pending Bookings View - [ ] Guest bookings appear in pending list alongside client bookings - [ ] Guest bookings show "Guest" badge/indicator - [ ] Guest name, email, phone displayed in list - [ ] Click through to booking review shows full guest details ### Admin Booking Review Page - [ ] Guest contact info displayed prominently - [ ] Guest name shown instead of user name - [ ] Guest email shown with mailto link - [ ] Guest phone shown with tel link - [ ] Approve/reject workflow works for guest bookings - [ ] Email notifications sent to guest on status change ### Existing Admin Email - [ ] `NewBookingAdminEmail` updated to handle guest bookings - [ ] Admin email shows guest contact info for guest bookings - [ ] Admin email shows client info for client bookings ## Implementation Steps ### Step 1: Create Guest Booking Submitted Email Create `app/Mail/GuestBookingSubmittedMail.php`: ```php $this->consultation, 'guestName' => $this->consultation->guest_name, ], ); } } ``` ### Step 2: Create Guest Booking Submitted Email Template Create `resources/views/emails/guest-booking-submitted.blade.php`: ```blade # {{ __('emails.guest_booking_submitted_title') }} {{ __('emails.guest_booking_submitted_greeting', ['name' => $guestName]) }} {{ __('emails.guest_booking_submitted_body') }} **{{ __('booking.date') }}:** {{ $consultation->booking_date->translatedFormat('l, d M Y') }} **{{ __('booking.time') }}:** {{ \Carbon\Carbon::parse($consultation->booking_time)->format('g:i A') }} **{{ __('booking.duration') }}:** 45 {{ __('common.minutes') }} {{ __('emails.guest_booking_submitted_next_steps') }} {{ __('emails.signature') }},
{{ config('app.name') }}
``` ### Step 3: Create Guest Booking Approved Email Create `app/Mail/GuestBookingApprovedMail.php`: ```php $this->consultation, 'guestName' => $this->consultation->guest_name, 'isPaid' => $this->consultation->consultation_type?->value === 'paid', ], ); } public function attachments(): array { $calendarService = app(CalendarService::class); $icsContent = $calendarService->generateIcs($this->consultation); return [ Attachment::fromData(fn () => $icsContent, 'consultation.ics') ->withMime('text/calendar'), ]; } } ``` ### Step 4: Create Guest Booking Rejected Email Create `app/Mail/GuestBookingRejectedMail.php`: ```php $this->consultation, 'guestName' => $this->consultation->guest_name, 'reason' => $this->reason, ], ); } } ``` ### Step 5: Update NewBookingAdminEmail Update `app/Mail/NewBookingAdminEmail.php` to handle guests: ```php public function content(): Content { return new Content( view: 'emails.new-booking-admin', with: [ 'consultation' => $this->consultation, 'clientName' => $this->consultation->getClientName(), 'clientEmail' => $this->consultation->getClientEmail(), 'clientPhone' => $this->consultation->getClientPhone(), 'isGuest' => $this->consultation->isGuest(), ], ); } ``` ### Step 6: Update Admin New Booking Email Template Update `resources/views/emails/new-booking-admin.blade.php`: ```blade # {{ __('emails.new_booking_admin_title') }} {{ __('emails.new_booking_admin_body') }} @if($isGuest) **{{ __('emails.booking_type') }}:** {{ __('emails.guest_booking') }} @endif **{{ __('booking.client_name') }}:** {{ $clientName }} **{{ __('booking.client_email') }}:** {{ $clientEmail }} **{{ __('booking.client_phone') }}:** {{ $clientPhone }} **{{ __('booking.date') }}:** {{ $consultation->booking_date->translatedFormat('l, d M Y') }} **{{ __('booking.time') }}:** {{ \Carbon\Carbon::parse($consultation->booking_time)->format('g:i A') }} **{{ __('booking.problem_summary') }}:** {{ $consultation->problem_summary }} {{ __('emails.review_booking') }} {{ __('emails.signature') }},
{{ config('app.name') }}
``` ### Step 7: Update Admin Pending Bookings List Update `resources/views/livewire/admin/bookings/pending.blade.php` to show guest indicator: In the table row, add guest badge: ```blade @if($consultation->isGuest()) {{ __('admin.guest') }}
{{ $consultation->guest_name }} @else {{ $consultation->user->name }} @endif @if($consultation->isGuest()) {{ $consultation->guest_email }} @else {{ $consultation->user->email }} @endif ``` ### Step 8: Update Admin Booking Review Page Update `resources/views/livewire/admin/bookings/review.blade.php`: ```blade {{-- Client/Guest Information Section --}}
{{ __('admin.client_information') }} @if($consultation->isGuest()) {{ __('admin.guest') }} @endif
{{ __('booking.client_name') }}
{{ $consultation->getClientName() }}
{{ __('booking.client_email') }}
{{ $consultation->getClientEmail() }}
{{ __('booking.client_phone') }}
{{ $consultation->getClientPhone() }}
@unless($consultation->isGuest())
{{ __('admin.client_type') }}
{{ $consultation->user->user_type->label() }}
@endunless
``` ### Step 9: Update Booking Approval Logic Update the approve method in the booking review component to send guest emails: ```php public function approve(): void { // ... existing approval logic ... // Send appropriate email based on guest/client if ($this->consultation->isGuest()) { Mail::to($this->consultation->guest_email)->queue( new GuestBookingApprovedMail($this->consultation) ); } else { Mail::to($this->consultation->user)->queue( new BookingApprovedMail($this->consultation) ); } } public function reject(): void { // ... existing rejection logic ... // Send appropriate email based on guest/client if ($this->consultation->isGuest()) { Mail::to($this->consultation->guest_email)->queue( new GuestBookingRejectedMail($this->consultation, $this->rejectionReason) ); } else { Mail::to($this->consultation->user)->queue( new BookingRejectedMail($this->consultation, $this->rejectionReason) ); } } ``` ### Step 10: Add Translation Keys Add to `lang/en/emails.php`: ```php 'guest_booking_submitted_subject' => 'Booking Request Received - Libra Law Firm', 'guest_booking_submitted_title' => 'Your Booking Request Has Been Received', 'guest_booking_submitted_greeting' => 'Dear :name,', 'guest_booking_submitted_body' => 'Thank you for your consultation request. We have received your booking and our team will review it shortly.', 'guest_booking_submitted_next_steps' => 'You will receive another email once your booking has been reviewed. If approved, you will receive the consultation details and a calendar invitation.', 'guest_booking_approved_subject' => 'Booking Confirmed - Libra Law Firm', 'guest_booking_rejected_subject' => 'Booking Update - Libra Law Firm', 'booking_type' => 'Booking Type', 'guest_booking' => 'Guest (No Account)', ``` Add to `lang/ar/emails.php`: ```php 'guest_booking_submitted_subject' => 'تم استلام طلب الحجز - مكتب ليبرا للمحاماة', 'guest_booking_submitted_title' => 'تم استلام طلب الحجز الخاص بك', 'guest_booking_submitted_greeting' => 'عزيزي/عزيزتي :name،', 'guest_booking_submitted_body' => 'شكراً لطلب الاستشارة. لقد تلقينا حجزك وسيقوم فريقنا بمراجعته قريباً.', 'guest_booking_submitted_next_steps' => 'ستتلقى رسالة أخرى عند مراجعة حجزك. في حال الموافقة، ستتلقى تفاصيل الاستشارة ودعوة تقويم.', 'guest_booking_approved_subject' => 'تأكيد الحجز - مكتب ليبرا للمحاماة', 'guest_booking_rejected_subject' => 'تحديث الحجز - مكتب ليبرا للمحاماة', 'booking_type' => 'نوع الحجز', 'guest_booking' => 'زائر (بدون حساب)', ``` Add to `lang/en/admin.php`: ```php 'guest' => 'Guest', 'client_information' => 'Client Information', 'client_type' => 'Client Type', ``` Add to `lang/ar/admin.php`: ```php 'guest' => 'زائر', 'client_information' => 'معلومات العميل', 'client_type' => 'نوع العميل', ``` ## Testing Requirements ### Email Tests ```php test('guest receives confirmation email on booking submission', function () { Mail::fake(); $consultation = Consultation::factory()->guest()->create([ 'status' => ConsultationStatus::Pending, ]); Mail::to($consultation->guest_email)->send( new GuestBookingSubmittedMail($consultation) ); Mail::assertSent(GuestBookingSubmittedMail::class, function ($mail) use ($consultation) { return $mail->hasTo($consultation->guest_email); }); }); test('guest receives approval email with calendar attachment', function () { Mail::fake(); $consultation = Consultation::factory()->guest()->create([ 'status' => ConsultationStatus::Approved, ]); Mail::to($consultation->guest_email)->send( new GuestBookingApprovedMail($consultation) ); Mail::assertSent(GuestBookingApprovedMail::class, function ($mail) { return count($mail->attachments()) === 1; }); }); test('admin email shows guest indicator for guest bookings', function () { Mail::fake(); $consultation = Consultation::factory()->guest()->create(); $admin = User::factory()->admin()->create(); Mail::to($admin)->send(new NewBookingAdminEmail($consultation)); Mail::assertSent(NewBookingAdminEmail::class); }); ``` ### Admin Interface Tests ```php test('admin can see guest bookings in pending list', function () { $admin = User::factory()->admin()->create(); $guestConsultation = Consultation::factory()->guest()->pending()->create(); Volt::test('admin.bookings.pending') ->actingAs($admin) ->assertSee($guestConsultation->guest_name) ->assertSee(__('admin.guest')); }); test('admin can view guest booking details', function () { $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->guest()->create([ 'guest_name' => 'Test Guest', 'guest_email' => 'test@example.com', ]); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->actingAs($admin) ->assertSee('Test Guest') ->assertSee('test@example.com') ->assertSee(__('admin.guest')); }); test('admin can approve guest booking', function () { Mail::fake(); $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->guest()->pending()->create(); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->actingAs($admin) ->call('approve'); expect($consultation->fresh()->status)->toBe(ConsultationStatus::Approved); Mail::assertQueued(GuestBookingApprovedMail::class); }); test('admin can reject guest booking', function () { Mail::fake(); $admin = User::factory()->admin()->create(); $consultation = Consultation::factory()->guest()->pending()->create(); Volt::test('admin.bookings.review', ['consultation' => $consultation]) ->actingAs($admin) ->set('rejectionReason', 'Not available for this time') ->call('reject'); expect($consultation->fresh()->status)->toBe(ConsultationStatus::Rejected); Mail::assertQueued(GuestBookingRejectedMail::class); }); ``` ## Dependencies - Story 11.1 (Database Schema & Model Updates) - Story 11.2 (Public Booking Form) ## Definition of Done - [ ] Guest booking submitted email created and working - [ ] Guest booking approved email created with calendar attachment - [ ] Guest booking rejected email created - [ ] Admin new booking email updated for guests - [ ] Admin pending bookings shows guest indicator - [ ] Admin booking review shows guest contact info - [ ] Approve/reject sends correct email (guest vs client) - [ ] All translations in place (Arabic/English) - [ ] All email tests pass - [ ] All admin interface tests pass - [ ] Existing client booking emails unchanged