# 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() }}
@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