5.6 KiB
5.6 KiB
Libra - Coding Standards
Extracted from
docs/architecture.mdSection 20
Critical Rules
| Rule | Description |
|---|---|
| Volt Pattern | Use class-based Volt components (per CLAUDE.md) |
| Flux UI | Use Flux components when available |
| Form Validation | Always use Form Request or Livewire form objects |
| Eloquent | Prefer Model::query() over DB:: facade |
| Actions | Use single-purpose Action classes for business logic |
| Testing | Every feature must have corresponding Pest tests |
| Pint | Run vendor/bin/pint --dirty before committing |
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Models | Singular PascalCase | Consultation |
| Tables | Plural snake_case | consultations |
| Columns | snake_case | booking_date |
| Controllers | PascalCase + Controller | ConsultationController |
| Actions | Verb + Noun + Action | CreateConsultationAction |
| Jobs | Verb + Noun | SendBookingNotification |
| Events | Past tense | ConsultationApproved |
| Listeners | Verb phrase | SendApprovalNotification |
| Volt Components | kebab-case path | admin/consultations/index |
| Enums | PascalCase | ConsultationStatus |
| Enum Cases | PascalCase | NoShow |
| Traits | Adjective or -able | HasConsultations, Bookable |
| Interfaces | Adjective or -able | Exportable |
Volt Component Template
<?php
// resources/views/livewire/admin/consultations/index.blade.php
use App\Models\Consultation;
use App\Enums\ConsultationStatus;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new class extends Component {
use WithPagination;
public string $search = '';
public string $status = '';
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedStatus(): void
{
$this->resetPage();
}
public function with(): array
{
return [
'consultations' => Consultation::query()
->with('user:id,full_name,email')
->when($this->search, fn ($q) => $q->whereHas('user', fn ($q) =>
$q->where('full_name', 'like', "%{$this->search}%")
))
->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderBy('booking_date', 'desc')
->paginate(15),
'statuses' => ConsultationStatus::cases(),
];
}
}; ?>
<div>
<x-slot name="header">
<flux:heading size="xl">{{ __('models.consultations') }}</flux:heading>
</x-slot>
<div class="flex gap-4 mb-6">
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="{{ __('messages.search') }}"
icon="magnifying-glass"
/>
<flux:select wire:model.live="status">
<option value="">{{ __('messages.all_statuses') }}</option>
@foreach($statuses as $s)
<option value="{{ $s->value }}">{{ $s->label() }}</option>
@endforeach
</flux:select>
</div>
<div class="space-y-4">
@forelse($consultations as $consultation)
<x-ui.card wire:key="consultation-{{ $consultation->id }}">
{{-- Card content --}}
</x-ui.card>
@empty
<x-ui.empty-state :message="__('messages.no_consultations')" />
@endforelse
</div>
<div class="mt-6">
{{ $consultations->links() }}
</div>
</div>
Action Class Template
<?php
// app/Actions/Consultation/CreateConsultationAction.php
namespace App\Actions\Consultation;
use App\Models\Consultation;
use App\Models\User;
use App\Enums\ConsultationStatus;
use App\Enums\PaymentStatus;
use App\Jobs\SendBookingNotification;
use App\Exceptions\BookingLimitExceededException;
use Illuminate\Support\Facades\DB;
class CreateConsultationAction
{
public function execute(User $user, array $data): Consultation
{
// Validate booking limit
if ($user->hasBookingOnDate($data['booking_date'])) {
throw new BookingLimitExceededException(
__('messages.booking_limit_exceeded')
);
}
return DB::transaction(function () use ($user, $data) {
$consultation = Consultation::create([
'user_id' => $user->id,
'booking_date' => $data['booking_date'],
'booking_time' => $data['booking_time'],
'problem_summary' => $data['problem_summary'],
'status' => ConsultationStatus::Pending,
'payment_status' => PaymentStatus::NotApplicable,
]);
SendBookingNotification::dispatch($consultation);
return $consultation;
});
}
}
Testing Standards
- Use Pest 4 with
Volt::test()for Livewire components - Every feature must have corresponding tests
- Use factories with custom states when creating test models
- Follow existing test structure in
tests/Feature/andtests/Unit/
Test Example
use Livewire\Volt\Volt;
test('consultation list can be filtered by status', function () {
$user = User::factory()->admin()->create();
Volt::test('admin.consultations.index')
->actingAs($user)
->set('status', ConsultationStatus::Pending->value)
->assertHasNoErrors();
});
Bilingual Support
- All user-facing strings must use Laravel's
__()helper - Translations stored in
resources/lang/{ar,en}/ - Use JSON translations for simple strings, PHP arrays for structured data
- RTL layout handled via Tailwind CSS classes