Add all files

This commit is contained in:
root
2025-11-30 11:57:22 +01:00
parent 2ee51cac4f
commit 044271db3d
1476 changed files with 142544 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Pages;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Group;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Paymenter\Extensions\Others\ComplianceTools\Services\SupportPinService;
use Illuminate\Support\Facades\Auth;
use App\Admin\Resources\UserResource;
class ValidateSupportPin extends Page implements HasForms
{
use InteractsWithForms;
public ?string $pin = null; // <-- add this
public ?array $validated = null;
public ?string $errorMessage = null;
protected static ?string $title = 'Validate Support PIN';
protected static ?string $navigationIcon = 'ri-customer-service-2-line';
protected static ?string $activeNavigationIcon = 'ri-customer-service-2-fill';
protected static ?string $navigationGroup = 'Compliance';
protected static ?string $slug = 'compliance/validate-pin';
protected static ?string $navigationLabel = 'Validate Support PIN';
protected static string $view = 'compliance-tools::admin.validate-support-pin';
public function mount(): void
{
$this->form->fill([
'pin' => '',
]);
}
protected function getFormSchema(): array
{
return [
TextInput::make('pin')
->label('Support PIN')
->required()
->minLength(4)
->maxLength(16)
->placeholder('Enter PIN shared by client')
->autocomplete('off'),
];
}
public function validatePin(SupportPinService $service): void
{
$pin = $this->pin; // <-- access the bound property
$this->validated = null;
$this->errorMessage = null;
$record = $service->validate($pin);
if (!$record) {
$this->errorMessage = 'No active PIN matched the value provided.';
Notification::make()
->title('Support PIN not found or expired')
->danger()
->send();
return;
}
$this->validated = [
'user_name' => $record->user?->name,
'user_email' => $record->user?->email,
'expires_at' => $record->expires_at?->toDayDateTimeString(),
'last_validated_at' => $record->last_validated_at?->toDayDateTimeString(),
'user_id' => $record->user?->getKey(),
'user_url' => $record->user ? UserResource::getUrl('edit', ['record' => $record->user]) : null,
];
Notification::make()
->title('Support PIN validated')
->success()
->send();
}
public static function canAccess(): bool
{
$user = Auth::user();
return $user && $user->hasPermission('admin.compliance.validate_pin');
}
// Required to avoid Filament 2 breadcrumb error
public function getBreadcrumbs(): array
{
return [];
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources;
use Filament\Resources\Resource;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\AuditLogResource\Pages\ListAuditLogs;
use Paymenter\Extensions\Others\ComplianceTools\Models\AuditLog;
class AuditLogResource extends Resource
{
protected static ?string $model = AuditLog::class;
protected static ?string $navigationIcon = 'ri-clipboard-line';
protected static ?string $activeNavigationIcon = 'ri-clipboard-fill';
protected static ?string $navigationGroup = 'Compliance';
protected static ?string $navigationLabel = 'Audit Logs';
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
TextColumn::make('category')
->label('Category')
->sortable()
->searchable()
->badge(),
TextColumn::make('event')
->label('Event')
->sortable()
->searchable()
->badge(),
TextColumn::make('subject.name')
->label('Subject')
->sortable()
->searchable(['subject.name', 'subject.email'])
->formatStateUsing(fn (?string $state, AuditLog $record) => $state ?? $record->subject?->email),
TextColumn::make('actor_type')
->label('Actor type')
->sortable()
->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : 'System'),
TextColumn::make('actor_id')
->label('Actor ID')
->sortable(),
TextColumn::make('ip_address')
->label('IP')
->sortable(),
TextColumn::make('created_at')
->label('Logged at')
->dateTime()
->sortable(),
])
->filters([
SelectFilter::make('category')
->options(fn () => AuditLog::query()->distinct()->pluck('category', 'category')->toArray()),
]);
}
public static function getPages(): array
{
return [
'index' => ListAuditLogs::route('/'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\AuditLogResource\Pages;
use Filament\Resources\Pages\ListRecords;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\AuditLogResource;
class ListAuditLogs extends ListRecords
{
protected static string $resource = AuditLogResource::class;
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources;
use Filament\Forms\Components\Textarea;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\ViewEntry;
use Filament\Resources\Resource;
use Filament\Tables\Table;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Actions\Action as TableAction;
use Illuminate\Database\Eloquent\Model;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource\Pages\ListDeletionRequests;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource\Pages\ViewDeletionRequest;
use Paymenter\Extensions\Others\ComplianceTools\Models\AccountDeletionRequest;
class DeletionRequestResource extends Resource
{
protected static ?string $model = AccountDeletionRequest::class;
protected static ?string $navigationIcon = 'ri-user-unfollow-line';
protected static ?string $activeNavigationIcon = 'ri-user-unfollow-fill';
protected static ?string $navigationGroup = 'Compliance';
protected static ?string $navigationLabel = 'Deletion Requests';
protected static bool $shouldRegisterNavigation = true; // Ensure it shows in navigation
// Ensure Filament can show this resource in the navigation
public static function canView(?Model $record = null): bool
{
return true;
}
// Forms
public static function getFormSchema(): array
{
return [
Textarea::make('admin_notes')->label('Admin Notes'),
];
}
// Infolists
public static function getInfolistSchema(): array
{
return [
TextEntry::make('client')
->label('Client')
->state(fn (AccountDeletionRequest $record) => $record?->user?->name ?? '—'),
TextEntry::make('email')
->label('Email')
->state(fn (AccountDeletionRequest $record) => $record?->user?->email ?? '—')
->copyable(),
TextEntry::make('status')
->label('Status')
->badge()
->color(fn (AccountDeletionRequest $record) => match ($record->status) {
AccountDeletionRequest::STATUS_COMPLETED => 'success',
AccountDeletionRequest::STATUS_DENIED => 'danger',
AccountDeletionRequest::STATUS_APPROVED => 'warning',
default => 'info',
}),
TextEntry::make('reason')
->label('Reason')
->state(fn (AccountDeletionRequest $record) => $record->reason ?? '—')
->columnSpanFull()
->extraAttributes(['class' => 'whitespace-pre-wrap']),
TextEntry::make('requested_at')
->label('Requested at')
->state(fn (AccountDeletionRequest $record) => optional($record->created_at)?->toDayDateTimeString() ?? '—'),
TextEntry::make('processed_at')
->label('Processed at')
->state(fn (AccountDeletionRequest $record) => optional($record->processed_at)?->toDayDateTimeString() ?? '—'),
TextEntry::make('processed_by')
->label('Processed by')
->state(fn (AccountDeletionRequest $record) => $record?->processor?->name ?? '—'),
TextEntry::make('admin_notes')
->label('Admin notes')
->state(fn (AccountDeletionRequest $record) => $record->admin_notes ?? '—')
->columnSpanFull()
->extraAttributes(['class' => 'whitespace-pre-wrap']),
ViewEntry::make('metadata')
->label('Request metadata')
->view('admin.infolists.components.json')
->state(fn (AccountDeletionRequest $record) => $record->metadata)
->columnSpanFull(),
ViewEntry::make('snapshot')
->label('Snapshot')
->view('admin.infolists.components.json')
->state(fn (AccountDeletionRequest $record) => $record->snapshot)
->columnSpanFull(),
];
}
// Table
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('user.name')
->label('Client')
->searchable()
->sortable(),
TextColumn::make('user.email')
->label('Email')
->searchable()
->sortable(),
TextColumn::make('status')
->label('Status')
->badge()
->color(fn (string $state) => match ($state) {
AccountDeletionRequest::STATUS_COMPLETED => 'success',
AccountDeletionRequest::STATUS_DENIED => 'danger',
AccountDeletionRequest::STATUS_APPROVED => 'warning',
default => 'info',
})
->icon(fn (string $state) => match ($state) {
AccountDeletionRequest::STATUS_COMPLETED => 'heroicon-o-check-circle',
AccountDeletionRequest::STATUS_DENIED => 'heroicon-o-x-circle',
AccountDeletionRequest::STATUS_APPROVED => 'heroicon-o-hand-raised',
default => 'heroicon-o-clock',
})
->sortable(),
TextColumn::make('created_at')
->label('Requested')
->dateTime()
->sortable(),
TextColumn::make('processed_at')
->label('Processed')
->dateTime()
->sortable(),
])
->filters([
SelectFilter::make('status')
->options([
AccountDeletionRequest::STATUS_PENDING => 'Pending',
AccountDeletionRequest::STATUS_APPROVED => 'Approved',
AccountDeletionRequest::STATUS_COMPLETED => 'Completed',
AccountDeletionRequest::STATUS_DENIED => 'Denied',
]),
])
->actions([
TableAction::make('view')
->label('View')
->icon('heroicon-o-eye')
->url(fn (AccountDeletionRequest $record): string => static::getUrl('view', ['record' => $record]))
->visible(fn () => static::getPages()['view'] ?? false),
]);
}
// Pages
public static function getPages(): array
{
return [
'index' => ListDeletionRequests::route('/'),
'view' => ViewDeletionRequest::route('/{record}'),
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource\Pages;
use Filament\Actions\Action;
use Filament\Resources\Pages\ListRecords;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource;
class ListDeletionRequests extends ListRecords
{
protected static string $resource = DeletionRequestResource::class;
protected function getActions(): array
{
return [
Action::make('refresh')
->label('Refresh')
->icon('heroicon-o-arrow-path')
->action(fn () => null),
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource\Pages;
use Filament\Actions\Action;
use Filament\Forms\Components\Textarea;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Support\Facades\Auth;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\DeletionRequestResource;
use Paymenter\Extensions\Others\ComplianceTools\Models\AccountDeletionRequest;
use Paymenter\Extensions\Others\ComplianceTools\Services\AccountDeletionService;
class ViewDeletionRequest extends ViewRecord
{
protected static string $resource = DeletionRequestResource::class;
protected function getHeaderActions(): array
{
return [
Action::make('approve')
->label('Approve & anonymise')
->icon('heroicon-o-check-circle')
->color('success')
->visible(fn () => $this->record->status !== AccountDeletionRequest::STATUS_COMPLETED)
->form([
Textarea::make('notes')->label('Internal notes')->rows(3),
])
->requiresConfirmation()
->action(function (array $data, AccountDeletionService $service) {
$service->approve($this->record, Auth::user(), $data['notes'] ?? null);
$this->record->refresh();
Notification::make()
->title('Account anonymised successfully')
->success()
->send();
})
->authorize(fn () => Auth::user()?->can('update', $this->record) ?? false),
Action::make('deny')
->label('Deny request')
->icon('heroicon-o-x-circle')
->color('danger')
->visible(fn () => $this->record->status === AccountDeletionRequest::STATUS_PENDING)
->form([
Textarea::make('notes')->label('Internal notes')->rows(3)->required(),
])
->requiresConfirmation()
->action(function (array $data, AccountDeletionService $service) {
$service->deny($this->record, Auth::user(), $data['notes'] ?? null);
$this->record->refresh();
Notification::make()
->title('Deletion request marked as denied')
->success()
->send();
})
->authorize(fn () => Auth::user()?->can('update', $this->record) ?? false),
];
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Actions\Action;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\SupportPinResource\Pages\ListSupportPins;
use Paymenter\Extensions\Others\ComplianceTools\Models\SupportPin;
class SupportPinResource extends Resource
{
protected static ?string $model = SupportPin::class;
protected static ?string $navigationIcon = 'ri-key-2-line';
protected static ?string $activeNavigationIcon = 'ri-key-2-fill';
protected static ?string $navigationGroup = 'Compliance';
protected static ?string $navigationLabel = 'Support PINs';
public static function table(Tables\Table $table): Tables\Table
{
return $table
->columns([
TextColumn::make('user.name')
->label('Client')
->searchable()
->sortable(),
TextColumn::make('user.email')
->label('Email')
->searchable()
->sortable(),
TextColumn::make('created_at')
->label('Generated')
->dateTime()
->sortable(),
TextColumn::make('expires_at')
->label('Expires')
->dateTime()
->sortable(),
TextColumn::make('last_validated_at')
->label('Last validated')
->dateTime()
->sortable(),
TextColumn::make('revoked_at')
->label('Revoked at')
->dateTime()
->sortable(),
BadgeColumn::make('status')
->label('Status')
->getStateUsing(fn (SupportPin $record) =>
$record->revoked_at ? 'Revoked' : ($record->expires_at->isPast() ? 'Expired' : 'Active')
)
->colors([
'success' => fn ($state) => $state === 'Active',
'warning' => fn ($state) => $state === 'Expired',
'danger' => fn ($state) => $state === 'Revoked',
]),
])
->filters([
Filter::make('active')
->label('Active only')
->query(fn ($query) => $query->whereNull('revoked_at')->where('expires_at', '>', now())),
])
->actions([
Action::make('revoke')
->label('Revoke')
->icon('heroicon-o-no-symbol')
->color('danger')
->requiresConfirmation()
->visible(fn (SupportPin $record) => is_null($record->revoked_at) && !$record->expires_at->isPast())
->action(fn (SupportPin $record) =>
app(\Paymenter\Extensions\Others\ComplianceTools\Services\SupportPinService::class)
->revoke($record, auth()->user())
),
]);
}
public static function getPages(): array
{
return [
'index' => ListSupportPins::route('/'),
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\SupportPinResource\Pages;
use Filament\Resources\Pages\ListRecords;
use Paymenter\Extensions\Others\ComplianceTools\Admin\Resources\SupportPinResource;
class ListSupportPins extends ListRecords
{
protected static string $resource = SupportPinResource::class;
// Optional: disable breadcrumbs if you are not using them in Filament 2
protected static bool $hasBreadcrumbs = false;
// Optional: set a custom title for the page
protected static ?string $title = 'Support PINs';
// Optional: override the breadcrumbs function if needed
public function getBreadcrumbs(): array
{
return [];
}
}