limitReached = Auth::user()->certificateLimitReached(); } /** * Runs when a file is selected. If OCR is enabled for the user, analyze * the uploaded document and pre-fill empty fields with suggestions. */ public function updatedFile(): void { $this->validate([ 'file' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'], ]); $user = Auth::user(); if ($user->ocr_provider === 'none' || ! $user->plan()->includesOcr()) { return; } $this->scanning = true; $suggestions = app(OcrManager::class) ->driver($user->ocr_provider) ->analyze($this->file->getRealPath(), $user->ocr_api_key); if (blank($this->title) && filled($suggestions['title'] ?? null)) { $this->title = $suggestions['title']; } if (blank($this->expires_at) && filled($suggestions['expires_at'] ?? null)) { $this->expires_at = $suggestions['expires_at']; } $this->scanning = false; } public function save(): void { // Re-check server-side even though the form is hidden once the // limit is reached, in case the plan's limit was hit between // page load and submit (e.g. another tab, or a downgrade). if (Auth::user()->certificateLimitReached()) { $this->limitReached = true; return; } $validated = $this->validate([ 'file' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'], 'title' => ['required', 'string', 'max:255'], 'category_id' => ['nullable', 'integer', 'exists:categories,id'], 'issuer' => ['nullable', 'string', 'max:255'], 'certificate_number' => ['nullable', 'string', 'max:255'], 'holder_last_name' => ['nullable', 'string', 'max:255'], 'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'], 'expires_at' => ['required', 'date'], 'notes' => ['nullable', 'string', 'max:2000'], ]); $path = $this->file->storeAs( 'certificates/'.Auth::id(), Str::uuid().'.'.$this->file->getClientOriginalExtension(), 'local' ); Auth::user()->certificates()->create([ 'title' => $validated['title'], 'category_id' => $validated['category_id'] ?: null, 'issuer' => $validated['issuer'] ?: null, 'certificate_number' => $validated['certificate_number'] ?: null, 'holder_last_name' => $validated['holder_last_name'] ?: null, 'issued_at' => $validated['issued_at'] ?: null, 'file_path' => $path, 'expires_at' => $validated['expires_at'], 'notes' => $validated['notes'] ?: null, ]); Flux::toast(variant: 'success', text: __('Certificate added.')); $this->redirectRoute('certificates.index', navigate: true); } /** * @return Collection */ #[Computed] public function categories(): Collection { return Category::orderBy('name')->get(); } }; ?>
{{ __('Add certificate') }} {{ __('Upload a document to preview it, then confirm the details — pre-filled automatically if scanning is enabled.') }}
@if ($limitReached) {{ __('Your plan is full — upgrade to add more certificates.') }} {{ __('View plans') }} {{ __('Back to certificates') }} @else
{{-- Left: live document preview (client-side, before upload completes) --}}
{{ __('Select a document to preview it here.') }}
{{ __('Uploading…') }}
{{-- Right: form fields --}}
@if ($scanning) {{ __('Scanning document for suggestions…') }} @endif {{ __('Uncategorized') }} @foreach ($this->categories as $category) {{ $category->name }} @endforeach
{{ __('Save certificate') }} {{ __('Cancel') }}
@endif