a2zwebltd/laravel-customer-support
Portable Laravel helpdesk engine: support tickets with threaded replies, internal notes, attachments (Spatie MediaLibrary), agent assignment, SLA due dates and escalation, mail notifications, events, policies, Livewire + Flux UI, and optional Nova resources.
composer require a2zwebltd/laravel-customer-support
php artisan migrate
php artisan vendor:publish --tag=customer-support-config
// app/Models/User.php
use A2ZWeb\CustomerSupport\Concerns\HasSupportTickets;
class User extends Authenticatable implements HasMedia
{
use HasSupportTickets;
}
// app/Providers/AppServiceProvider.php
public function boot()
{
Gate::define('manage-support-tickets', fn (User $user) => $user->is_admin);
}
// routes/console.php
Schedule::command('support:escalate-overdue')->hourly();
/support (customer-facing) or /support/admin (agent dashboard)./support/new./support/new.manage-support-tickets gate)./support/admin.config/customer-support.php (customize routes, SLA hours, email templates).app/Models/SupportTicket.php and app/Models/SupportTicketMessage.php (extend for custom fields).resources/views/livewire/ (override or extend Flux components).app/Mail/ (customize notification emails).app/Policies/SupportTicketPolicy.php (fine-tune authorization).// Create a ticket via API or Livewire form
$ticket = auth()->user()->createTicket([
'subject' => 'Payment Issue',
'message' => 'My order #12345 is pending...',
'category_id' => 1,
'priority' => 'high',
]);
// Assign a ticket to an agent
$ticket->assignTo($agentUser);
// Reply to a ticket
$ticket->reply('Here’s the resolution:', ['is_internal' => false]);
// Add an internal note
$ticket->reply('Follow-up: Call customer.', ['is_internal' => true]);
config/customer-support.php:
'sla_hours' => [
'low' => 72, // 3 days
'normal' => 24, // 1 day
'high' => 8, // 8 hours
'urgent' => 2, // 2 hours
],
php artisan support:escalate-overdue
Overdue and notifies admins.Schedule::command('support:escalate-overdue')->hourly()).$ticket->addMedia($file)->toMediaCollection('ticket-attachments');
$ticket->reply('Check the attached docs.', ['attachments' => [$file]]);
app/Mail/ (e.g., TicketCreated.php).@component directives in Blade templates for Flux components.event(new TicketCreated($ticket));
// Automatically triggers mailables via Laravel’s event system.
SupportTicketResource and SupportTicketMessageResource appear in Nova if installed.// app/Nova/SupportTicketResource.php
public static $displayInNavigation = true;
public static $title = 'Support Tickets';
vendor/a2zwebltd/laravel-customer-support/resources/views/livewire/ to resources/views/livewire/.TicketList):
// resources/views/livewire/ticket-list.blade.php
@extends('livewire.flux::components.base')
@section('content')
<div>
<!-- Custom logic -->
@foreach($tickets as $ticket)
<x-flux.card>
{{ $ticket->subject }}
</x-flux.card>
@endforeach
</div>
@endsection
dark class or override Tailwind config to match your theme.// Migration
Schema::table('support_tickets', function (Blueprint $table) {
$table->string('custom_field')->nullable();
});
// app/Models/SupportTicket.php
protected $fillable = ['custom_field', ...];
// app/Models/SupportTicket.php
public function scopeForTenant($query, $tenantId)
{
return $query->where('tenant_id', $tenantId);
}
// app/Models/User.php
public function currentTenant()
{
return $this->tenant; // Assuming a `tenant()` relationship.
}
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('support/tickets', \A2ZWeb\CustomerSupport\Http\Controllers\TicketController::class);
});
TicketController or MessageController for custom logic.Livewire Dependency:
MediaLibrary Conflicts:
spatie/laravel-medialibrary for attachments. If your app uses a different media library (e.g., intervention/image), conflicts may arise.HasMedia trait or use a shared media collection.SLA Calculation Quirks:
created_at and use sla_hours config. If tickets are edited after creation, SLAs may not recalculate automatically.touch() to the ticket when updating critical fields:
$ticket->update(['subject' => 'Updated']);
$ticket->touch(); // Resets SLA timer
Nova Auto-Registration:
php artisan vendor:publish --tag=customer-support-config
to ensure resources are loaded.Email Queueing:
php artisan queue:work.Threaded Replies Pagination:
SupportTicketMessage model uses paginate() for replies. If you have many messages, consider adding with(['ticket', 'user']) to avoid N+1 queries:
$messages = $ticket->messages()->with(['ticket', 'user'])->paginate(20);
Ticket Not Showing in Admin Dashboard:
manage-support-tickets gate is defined and the user passes it.status is not Closed (filtered by default in the UI).Attachments Not Uploading:
spatie/laravel-medialibrary is properly configured in `How can I help you explore Laravel packages today?