edulazaro/wireschedule
Self-hosted Calendly alternative for Laravel: define availability per event in config, let visitors book free slots via a drop-in Livewire component, and keep bookings in your own database. Themeable, no third-party services.
Installation
composer require edulazaro/wireschedule
php artisan vendor:publish --provider="Edulazaro\Wireschedule\WirescheduleServiceProvider" --tag="wireschedule-config"
php artisan migrate
config/wireschedule.php) and run migrations to set up the database tables.Basic Setup
config/wireschedule.php:
'availability' => [
'timezone' => 'America/New_York',
'default_duration' => 30, // minutes
'slots_per_day' => 10,
'working_days' => ['mon', 'tue', 'wed', 'thu', 'fri'],
],
MeetingType model or Tinker:
php artisan tinker
>>> \App\Models\MeetingType::create(['name' => 'Consultation', 'duration' => 60]);
First Use Case: Embedding a Scheduler
@livewire('wireschedule::scheduler', ['meeting_type_id' => 1])
Config-Driven Availability
config/wireschedule.php to define global availability rules (timezone, durations, working days).MeetingType model:
class MeetingType extends Model {
protected $casts = [
'duration' => 'integer',
'working_days' => 'array',
];
}
public function getAvailableSlots() {
return \Edulazaro\Wireschedule\Models\Availability::where('meeting_type_id', $this->meeting_type_id)
->whereDate('date', '>=', now()->toDateString())
->get();
}
Livewire UI Integration
use Livewire\Component;
use Edulazaro\Wireschedule\Livewire\Scheduler;
class CustomScheduler extends Scheduler {
protected $rules = [
'name' => 'required|min:3',
'email' => 'required|email',
];
public function submitForm() {
$this->validate();
// Custom logic (e.g., send notification)
$this->dispatch('booking-submitted');
}
}
wire* theme system (e.g., wiremodal for booking confirmations):
@livewire('wireschedule::scheduler')
@push('scripts')
@wireScript('wireschedule::scheduler')
@endpush
Database-Driven Extensions
php artisan make:migration add_custom_fields_to_bookings_table --table=bookings
$booking->custom_field; // e.g., 'agenda_notes'
API Access (Optional)
Route::post('/api/book', [BookingController::class, 'store']);
Booking model to create slots:
$booking = \App\Models\Booking::create([
'meeting_type_id' => 1,
'user_id' => auth()->id(),
'starts_at' => now()->addHours(2),
'duration' => 60,
]);
Timezone Mismatches
config/wireschedule.php and your Laravel APP_TIMEZONE match. Use Carbon::setTimezone() in queries if needed:
$slots = Availability::where('date', '>=', now()->timezone('UTC')->toDateString())->get();
Livewire Component Isolation
wireschedule::scheduler with other Livewire components that modify the same DOM elements (e.g., conflicting IDs/classes).Database Conflicts
wireschedule migrations to avoid constraint errors.Caching Availability
$slots = Cache::remember("availability_{$meeting_type_id}", now()->addHours(1), function () {
return Availability::where(...)->get();
});
\DB::enableQueryLog();
$slots = Availability::where(...)->get();
\Log::info('Availability Query:', ['query' => \DB::getQueryLog()]);
wire:ignore or wire:key to debug UI updates:
<div wire:key="scheduler-{{ $meeting_type_id }}">
@livewire('wireschedule::scheduler', ['meeting_type_id' => $meeting_type_id])
</div>
php artisan wireschedule:validate-config (if the package includes this command) to catch misconfigurations early.Booking model’s create method or use model events:
\App\Models\Booking::created(function ($booking) {
// Send Slack/email notification
});
php artisan vendor:publish --tag=wireschedule-views
wire* theme classes (e.g., WireModal) in your assets:
/* resources/css/wireschedule.css */
.wire-scheduler .wire-modal { --modal-width: 500px; }
Schedule facade to pre-generate slots:
\Schedule::call(function () {
\Edulazaro\Wireschedule\Jobs\GenerateAvailability::dispatch();
})->daily();
Availability model:
protected static function booted() {
static::addGlobalScope('tenant', function (Builder $builder) {
$builder->where('tenant_id', \Tenant::current()->id);
});
}
How can I help you explore Laravel packages today?