Add settings logo upload and brand queue notification emails.
Deploy Ladill Queue / deploy (push) Successful in 55s
Deploy Ladill Queue / deploy (push) Successful in 55s
Let orgs manage logos in settings and render customer-facing mail with company branding. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Http\Controllers\Qms\Concerns\ScopesToAccount;
|
||||
use App\Models\Branch;
|
||||
use App\Services\Qms\AuditLogger;
|
||||
use App\Services\Qms\QmsPermissions;
|
||||
use App\Support\OrganizationBranding;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -48,6 +49,8 @@ class SettingsController extends Controller
|
||||
'notifications_enabled' => ['boolean'],
|
||||
'care_integration_enabled' => ['nullable', 'boolean'],
|
||||
'frontdesk_integration_enabled' => ['nullable', 'boolean'],
|
||||
'logo' => ['nullable', 'image', 'mimes:jpeg,png,jpg,webp,svg', 'max:2048'],
|
||||
'remove_logo' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
$settings = $organization->settings ?? [];
|
||||
@@ -63,9 +66,21 @@ class SettingsController extends Controller
|
||||
}
|
||||
$settings['integrations'] = $integrations;
|
||||
|
||||
$logoPath = $organization->logo_path;
|
||||
|
||||
if ($request->boolean('remove_logo')) {
|
||||
OrganizationBranding::deleteStoredLogo($organization);
|
||||
$logoPath = null;
|
||||
}
|
||||
|
||||
if ($request->hasFile('logo')) {
|
||||
$logoPath = OrganizationBranding::storeLogo($organization, $request->file('logo'));
|
||||
}
|
||||
|
||||
$organization->update([
|
||||
'name' => $validated['name'],
|
||||
'timezone' => $validated['timezone'],
|
||||
'logo_path' => $logoPath,
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ class ContactMessageMail extends Mailable
|
||||
public string $bodyText,
|
||||
public ?string $fromName = null,
|
||||
public ?string $replyToAddress = null,
|
||||
public ?string $logoUrl = null,
|
||||
public ?string $companyName = null,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
@@ -34,6 +36,8 @@ class ContactMessageMail extends Mailable
|
||||
with: [
|
||||
'bodyText' => $this->bodyText,
|
||||
'fromName' => $this->fromName,
|
||||
'logoUrl' => $this->logoUrl,
|
||||
'companyName' => $this->companyName,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Services\Comms;
|
||||
|
||||
use App\Mail\ContactMessageMail;
|
||||
use App\Models\Organization;
|
||||
use App\Support\OrganizationBranding;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
@@ -13,14 +15,21 @@ use Illuminate\Support\Facades\Mail;
|
||||
*/
|
||||
class EmailService
|
||||
{
|
||||
public function send(string $to, string $subject, string $body, ?string $fromName = null, ?string $replyTo = null): bool
|
||||
public function send(string $to, string $subject, string $body, ?string $fromName = null, ?string $replyTo = null, ?Organization $organization = null): bool
|
||||
{
|
||||
if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$logoUrl = null;
|
||||
$companyName = null;
|
||||
if ($organization) {
|
||||
$logoUrl = OrganizationBranding::emailLogoUrl($organization);
|
||||
$companyName = $organization->name;
|
||||
}
|
||||
|
||||
try {
|
||||
Mail::to($to)->send(new ContactMessageMail($subject, $body, $fromName, $replyTo));
|
||||
Mail::to($to)->send(new ContactMessageMail($subject, $body, $fromName, $replyTo, $logoUrl, $companyName));
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -47,7 +47,8 @@ class QueueNotificationService
|
||||
public function appointmentReminder(\App\Models\QueueAppointment $appointment): void
|
||||
{
|
||||
$message = "Reminder: your appointment at {$appointment->scheduled_at->format('M j, H:i')} is coming up. Ref: {$appointment->reference}";
|
||||
$this->sendToContact($appointment->customer_phone, $appointment->customer_email, $message);
|
||||
$organization = $appointment->organization;
|
||||
$this->sendToContact($appointment->customer_phone, $appointment->customer_email, $message, $organization);
|
||||
}
|
||||
|
||||
protected function notify(Ticket $ticket, string $event, string $message): void
|
||||
@@ -56,7 +57,8 @@ class QueueNotificationService
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendToContact($ticket->customer_phone, $ticket->customer_email, $message);
|
||||
$organization = $ticket->organization ?? $ticket->serviceQueue?->organization;
|
||||
$this->sendToContact($ticket->customer_phone, $ticket->customer_email, $message, $organization);
|
||||
|
||||
Log::info('Queue notification sent', [
|
||||
'event' => $event,
|
||||
@@ -64,13 +66,13 @@ class QueueNotificationService
|
||||
]);
|
||||
}
|
||||
|
||||
protected function sendToContact(?string $phone, ?string $email, string $message): void
|
||||
protected function sendToContact(?string $phone, ?string $email, string $message, ?\App\Models\Organization $organization = null): void
|
||||
{
|
||||
if ($phone) {
|
||||
$this->sms->send($phone, $message);
|
||||
}
|
||||
if ($email) {
|
||||
$this->email->send($email, 'Ladill Queue update', $message);
|
||||
$this->email->send($email, 'Ladill Queue update', $message, null, null, $organization);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,4 +54,27 @@ class OrganizationBranding
|
||||
Storage::disk('public')->delete($organization->logo_path);
|
||||
}
|
||||
}
|
||||
|
||||
public static function emailLogoUrl(Organization $organization): ?string
|
||||
{
|
||||
if (! self::hasCustomLogo($organization)) {
|
||||
return null;
|
||||
}
|
||||
$ext = strtolower(pathinfo($organization->logo_path, PATHINFO_EXTENSION));
|
||||
if ($ext === 'svg') {
|
||||
return null;
|
||||
}
|
||||
$version = $organization->updated_at?->getTimestamp() ?? time();
|
||||
|
||||
return Storage::disk('public')->url($organization->logo_path).'?v='.$version;
|
||||
}
|
||||
|
||||
public static function wrapEmailHtml(string $bodyHtml, Organization $organization): string
|
||||
{
|
||||
return view('email.customer-branded', [
|
||||
'logoUrl' => self::emailLogoUrl($organization),
|
||||
'companyName' => $organization->name,
|
||||
'bodyHtml' => $bodyHtml,
|
||||
])->render();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user