*/ public array $selected = []; public string $shareEmail = ''; public int $shareExpiresInDays = 7; public bool $sharePasswordProtect = true; public ?string $createdShareUrl = null; public ?string $createdSharePassword = null; public function updatedCategoryFilter(): void { $this->resetPage(); } public function sort(string $column): void { if (! in_array($column, self::SORTABLE_COLUMNS, true)) { return; } if ($this->sortBy === $column) { $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; } else { $this->sortBy = $column; $this->sortDirection = 'asc'; } $this->resetPage(); } public function delete(int $certificateId): void { $certificate = Certificate::findOrFail($certificateId); if (filled($certificate->file_path)) { Storage::disk('local')->delete($certificate->file_path); } $certificate->delete(); Flux::modals()->close(); Flux::toast(variant: 'success', text: __('Certificate deleted.')); } public function share(): void { $validated = $this->validate([ 'shareEmail' => ['nullable', 'email'], 'shareExpiresInDays' => ['required', Rule::in([1, 7, 30])], 'sharePasswordProtect' => ['boolean'], 'selected' => ['required', 'array', 'min:1'], 'selected.*' => ['integer'], ]); // Throttle emails to protect the mail server from being used as a relay. if (filled($validated['shareEmail'])) { $key = 'share-certificates:'.Auth::id(); if (RateLimiter::tooManyAttempts($key, maxAttempts: 10)) { $this->addError('shareEmail', __('Too many share attempts. Please try again later.')); return; } RateLimiter::hit($key, decaySeconds: 3600); } $certificates = Certificate::whereIn('id', $validated['selected']) ->whereNotNull('file_path') ->get(); if ($certificates->isEmpty()) { $this->addError('selected', __('The selected certificates have no files to share.')); return; } $password = $validated['sharePasswordProtect'] ? Str::password(12, symbols: false) : null; $share = Share::create([ 'token' => Str::random(48), 'recipient_email' => $validated['shareEmail'] ?: null, 'password' => $password, 'expires_at' => now()->addDays($validated['shareExpiresInDays']), ]); $share->certificates()->attach($certificates->modelKeys()); if (filled($validated['shareEmail'])) { Mail::to($validated['shareEmail'])->send(new ShareCreatedMail( senderName: Auth::user()->name, shareUrl: $share->url(), expiresAt: $share->expires_at->toDayDateTimeString(), certificateCount: $certificates->count(), hasPassword: $password !== null, )); } // Show the link (and password) so the user can pass them on; the // password should travel through a separate channel than the link. $this->createdShareUrl = $share->url(); $this->createdSharePassword = $password; $this->reset('selected', 'shareEmail'); Flux::modal('share')->close(); Flux::modal('share-created')->show(); } public function dismissCreatedShare(): void { $this->reset('createdShareUrl', 'createdSharePassword'); } /** * @return LengthAwarePaginator */ #[Computed] public function certificates(): LengthAwarePaginator { $sortBy = in_array($this->sortBy, self::SORTABLE_COLUMNS, true) ? $this->sortBy : 'expires_at'; $sortDirection = $this->sortDirection === 'desc' ? 'desc' : 'asc'; return Certificate::query() ->with('category') ->when($this->categoryFilter !== '', fn ($query) => $query->where('category_id', $this->categoryFilter)) ->orderBy($sortBy, $sortDirection) ->paginate(10); } /** * @return Collection */ #[Computed] public function categories(): Collection { return Category::orderBy('name')->get(); } }; ?>
{{ __('Certificates') }} {{ __('An overview of your certificates and their expiry status.') }}
{{ __('All categories') }} @foreach ($this->categories as $category) {{ $category->name }} @endforeach {{ __('Add certificate') }}
@if (count($selected) > 0) {{ trans_choice(':count certificate selected|:count certificates selected', count($selected), ['count' => count($selected)]) }} {{ __('Share') }} @endif @if ($this->certificates->isEmpty()) {{ __('No certificates found') }} {{ $categoryFilter !== '' ? __('No certificates in this category. Try another filter.') : __('Add your first certificate to get started.') }} @else
{{ __('Title') }} {{ __('Certificate number') }} {{ __('Category') }} {{ __('Expires') }} {{ __('Status') }} @foreach ($this->certificates as $certificate) @php($status = $certificate->expiryStatus(auth()->user()->reminder_months_before)) {{ $certificate->title }} {{ $certificate->certificate_number ?? '—' }} {{ $certificate->category?->name ?? '—' }} {{ $certificate->expires_at->format('M j, Y') }} {{ $status->label() }} {{ __('Download') }} {{ __('Edit') }} {{ __('Delete') }}
{{ __('Delete certificate?') }} {{ __('":title" and its stored file will be permanently deleted.', ['title' => $certificate->title]) }}
{{ __('Cancel') }} {{ __('Delete') }}
@endforeach
@endif {{-- Share modal --}}
{{ __('Share certificates') }} {{ __('Create a secure page where the recipient can view and download the selected certificates.') }}
{{ __('1 day') }} {{ __('7 days') }} {{ __('30 days') }}
{{ __('Cancel') }} {{ __('Create share') }}
{{-- Share created modal --}}
{{ __('Share created') }} {{ __('The recipient can view and download the certificates on this page:') }}
@if ($createdShareUrl) @endif @if ($createdSharePassword) {{ __('Share this password with the recipient through a different channel (SMS or WhatsApp). You can look it up later on the Shares page.') }} @endif
{{ __('Done') }}