fd3a195453
Access group (Filament):
- StaffResource: manage users with an admin-tier role, gated by manage_staff
- CustomerResource: read-only view of role-less users, gated by view_customers
- RoleResource: edit permissions per role (fixed role set), gated by manage_roles
- ManageAppSettings: tabbed General/Booking settings page that reads/writes
real .env keys via new EnvFileWriter (no parallel DB settings table, so
BookingService/config('booking.*') stay unchanged)
- Moved Access above Catalog in the nav group order
- New permissions: manage_staff, manage_roles, view_customers, manage_settings
Booking soft deletes:
- bookings.deleted_at + SoftDeletes on the Booking model
- BookingPolicy::delete (manage_bookings, cancelled/expired only) and
::restore (manage_bookings)
- DeleteBookingTableAction/RestoreBookingTableAction + TrashedFilter on
BookingsTable, using authorize() so the policy is enforced at call time,
not just cosmetically hidden
Refund crash fix:
- ProcessRefundAction passed a nullable $payment->booking into
RefundBookingAction's non-nullable Booking param — a soft-deleted
booking's payment reaching the refund picker was an uncaught TypeError.
Excluded such payments from the picker and added a defensive guard.
- Same unguarded $event->payment->booking / $event->refund->payment->booking
pattern fixed in the MarkBookingPaid/MarkBookingRefunded queued listeners.
289 tests passing.
152 lines
6.3 KiB
PHP
152 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace Modules\Identity\Filament\Pages;
|
|
|
|
use BackedEnum;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Schemas\Components\Actions;
|
|
use Filament\Schemas\Components\Form;
|
|
use Filament\Schemas\Components\Tabs;
|
|
use Filament\Schemas\Components\Tabs\Tab;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Support\Icons\Heroicon;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Modules\Shared\Support\EnvFileWriter;
|
|
use UnitEnum;
|
|
|
|
/**
|
|
* Edits real env-backed config values (config('app.*'), config('booking.*'))
|
|
* in place via EnvFileWriter, rather than introducing a parallel DB-backed
|
|
* settings table — so BookingService and everything else that already reads
|
|
* config('booking.*') keeps working unchanged (domain.md §2).
|
|
*
|
|
* Requires the .env file to be writable by the app process; if it isn't
|
|
* (e.g. some production containers ship a read-only filesystem), saving
|
|
* will throw and the admin needs to edit .env directly on that host instead.
|
|
*/
|
|
class ManageAppSettings extends Page
|
|
{
|
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCog6Tooth;
|
|
|
|
protected static string|UnitEnum|null $navigationGroup = 'Access';
|
|
|
|
protected static ?string $navigationLabel = 'App Settings';
|
|
|
|
protected static ?string $title = 'App Settings';
|
|
|
|
protected string $view = 'identity::filament.pages.manage-app-settings';
|
|
|
|
/**
|
|
* @var array<string, mixed>|null
|
|
*/
|
|
public ?array $data = [];
|
|
|
|
public static function canAccess(): bool
|
|
{
|
|
return auth()->user()?->can('manage_settings') ?? false;
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->form->fill([
|
|
'site_name' => config('app.name'),
|
|
'support_email' => config('app.support_email'),
|
|
'support_phone' => config('app.support_phone'),
|
|
'timezone' => config('app.timezone'),
|
|
'currency' => config('app.currency'),
|
|
'back_seat_enabled' => (bool) config('booking.back_seat_enabled'),
|
|
'whole_vehicle_enabled' => (bool) config('booking.whole_vehicle_enabled'),
|
|
'front_seat_max_per_booking' => config('booking.front_seat_max_per_booking'),
|
|
]);
|
|
}
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->components([
|
|
Form::make([
|
|
Tabs::make('Settings')
|
|
->tabs([
|
|
Tab::make('General')
|
|
->schema([
|
|
TextInput::make('site_name')
|
|
->label('Site Name')
|
|
->required()
|
|
->maxLength(255),
|
|
TextInput::make('support_email')
|
|
->label('Support Email')
|
|
->email()
|
|
->maxLength(255),
|
|
TextInput::make('support_phone')
|
|
->label('Support Phone')
|
|
->tel()
|
|
->maxLength(255),
|
|
TextInput::make('timezone')
|
|
->label('Timezone')
|
|
->required()
|
|
->maxLength(64)
|
|
->helperText('A valid PHP timezone identifier, e.g. Asia/Yangon.'),
|
|
TextInput::make('currency')
|
|
->label('Currency Code')
|
|
->required()
|
|
->maxLength(3)
|
|
->helperText('ISO 4217 currency code, e.g. MMK.'),
|
|
])
|
|
->columns(2),
|
|
Tab::make('Booking')
|
|
->schema([
|
|
Toggle::make('back_seat_enabled')
|
|
->label('Back Seat Enabled')
|
|
->helperText('Whether customers can select Back Seat at all right now.'),
|
|
Toggle::make('whole_vehicle_enabled')
|
|
->label('Whole Vehicle Enabled')
|
|
->helperText('Whether customers can select Whole Vehicle at all right now.'),
|
|
TextInput::make('front_seat_max_per_booking')
|
|
->label('Front Seat Max Per Booking')
|
|
->numeric()
|
|
->minValue(1)
|
|
->required()
|
|
->helperText('Max Front Seats a single booking may request.'),
|
|
]),
|
|
]),
|
|
])
|
|
->livewireSubmitHandler('save')
|
|
->footer([
|
|
Actions::make([
|
|
Action::make('save')
|
|
->submit('save')
|
|
->keyBindings(['mod+s']),
|
|
]),
|
|
]),
|
|
])
|
|
->statePath('data');
|
|
}
|
|
|
|
public function save(EnvFileWriter $writer): void
|
|
{
|
|
$state = $this->form->getState();
|
|
|
|
$writer->write([
|
|
'APP_NAME' => $state['site_name'],
|
|
'SUPPORT_EMAIL' => $state['support_email'],
|
|
'SUPPORT_PHONE' => $state['support_phone'],
|
|
'APP_TIMEZONE' => $state['timezone'],
|
|
'APP_CURRENCY' => $state['currency'],
|
|
'BOOKING_BACK_SEAT_ENABLED' => (bool) $state['back_seat_enabled'],
|
|
'BOOKING_WHOLE_VEHICLE_ENABLED' => (bool) $state['whole_vehicle_enabled'],
|
|
'BOOKING_FRONT_SEAT_MAX_PER_BOOKING' => (int) $state['front_seat_max_per_booking'],
|
|
]);
|
|
|
|
Artisan::call('config:clear');
|
|
|
|
Notification::make()
|
|
->title('Settings saved')
|
|
->success()
|
|
->send();
|
|
}
|
|
}
|