release: v0.1.6 — Phase 06 Dashboard Upload Flow#90
Conversation
…converter-component CONV-071: Create DashboardConverter component
…-route-to-livewire-component CONV-072: Connect dashboard route to Livewire component
…ad-state CONV-073: Render empty upload state
…pload-flow CONV-074: Test valid file upload flow
…e-upload-handling CONV-075: Implement Livewire upload handling
…ile-summary CONV-076: Render uploaded file summary
…ded-file-action CONV-077: Add replace uploaded file action
…ed-file-action CONV-078: Add remove uploaded file action
…load-ui CONV-079: Add drag hover upload UI
…states CONV-080: Add upload error states
…g-state CONV-081: Add upload loading state
…laceholder CONV-082: Add format step placeholder
…oad-flow-smoke-tests CONV-083: Add dashboard upload flow smoke tests
📝 WalkthroughWalkthroughThis PR introduces ChangesDashboard File Upload Feature
Sequence DiagramsequenceDiagram
participant User
participant DashboardConverter
participant StoreUploadedFileAction
participant Storage
participant Database
User->>DashboardConverter: Upload file via drop/input
DashboardConverter->>DashboardConverter: Validate (required, file, max size)
alt Validation fails
DashboardConverter->>User: Display validation error
else Validation passes
DashboardConverter->>StoreUploadedFileAction: handle(user, uploadedFile)
StoreUploadedFileAction->>Storage: Store file
Storage-->>StoreUploadedFileAction: File stored
StoreUploadedFileAction->>Database: Create FileRecord
Database-->>StoreUploadedFileAction: FileRecord created
alt Exception (unsupported format/storage error)
StoreUploadedFileAction-->>DashboardConverter: Exception
DashboardConverter->>User: Display uploadError, reset to upload step
else Success
StoreUploadedFileAction-->>DashboardConverter: FileRecord with ID
DashboardConverter->>DashboardConverter: Set currentFileId, advance to format step
DashboardConverter->>User: Render format step with file summary
User->>DashboardConverter: Click replaceFile or removeFile
DashboardConverter->>DashboardConverter: Clear currentFileId and reset to upload step
DashboardConverter->>User: Return to upload step
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/Livewire/Dashboard/DashboardConverter.php`:
- Around line 61-75: In replaceFile() and removeFile() call a dedicated action
in app/Actions to delete the persisted upload (e.g., DeleteUploadedFileAction)
before calling resetCurrentUpload(); locate the current upload identifier
(currentFileId or upload) inside DashboardConverter and invoke the action (via
constructor injection or app()->make()) to remove the file_record and underlying
blob, handle any errors/exceptions (log or set uploadError) and only then call
resetCurrentUpload() to clear Livewire state; keep the deletion logic inside the
new action so the component stays thin.
In `@resources/views/livewire/dashboard/dashboard-converter.blade.php`:
- Around line 5-25: The dropzone currently only toggles isDragging on x-on:drop
and doesn't prevent default or transfer files to the hidden file input
(wire:model="upload"), so dropped files won't upload and the browser may
navigate away; update the x-on:drop handler on the drop container to
preventDefault/stopPropagation, extract files from event.dataTransfer.files, and
programmatically assign them to the hidden input bound by wire:model="upload"
(or call Livewire.upload/dispatch a custom event to invoke the Livewire upload
handler such as storeUpload) so the dropped files are selected and processed
just like a normal file input; ensure the x-on:dragover and x-on:drop handlers
both call event.preventDefault() to stop default navigation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5bd6bfe7-1c68-41bd-b546-c7191d1b5454
📒 Files selected for processing (6)
app/Livewire/Dashboard/DashboardConverter.phpresources/views/dashboard.blade.phpresources/views/livewire/dashboard/dashboard-converter.blade.phptests/Feature/DashboardRouteTest.phptests/Feature/Livewire/DashboardConverterTest.phptests/Feature/Livewire/DashboardConverterUploadTest.php
| public function replaceFile(): void | ||
| { | ||
| $this->resetCurrentUpload(); | ||
| } | ||
|
|
||
| public function removeFile(): void | ||
| { | ||
| $this->resetCurrentUpload(); | ||
| } | ||
|
|
||
| private function resetCurrentUpload(): void | ||
| { | ||
| $this->reset('upload', 'currentFileId', 'uploadError'); | ||
| $this->resetErrorBag(); | ||
| $this->step = 'upload'; |
There was a problem hiding this comment.
Delete the persisted upload before clearing component state.
By this point the file has already been stored via StoreUploadedFileAction, but replaceFile()/removeFile() only null out Livewire state. That leaves orphaned file_records rows and blobs behind, so “Remove” does not actually remove anything and repeated replacements will leak storage.
A small follow-up action here is to delegate cleanup to an app/Actions delete/remove action before resetCurrentUpload(). As per coding guidelines, "Controllers and Livewire components must stay thin - business logic goes into app/Actions".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/Livewire/Dashboard/DashboardConverter.php` around lines 61 - 75, In
replaceFile() and removeFile() call a dedicated action in app/Actions to delete
the persisted upload (e.g., DeleteUploadedFileAction) before calling
resetCurrentUpload(); locate the current upload identifier (currentFileId or
upload) inside DashboardConverter and invoke the action (via constructor
injection or app()->make()) to remove the file_record and underlying blob,
handle any errors/exceptions (log or set uploadError) and only then call
resetCurrentUpload() to clear Livewire state; keep the deletion logic inside the
new action so the component stays thin.
| <div | ||
| x-data="{ isDragging: false }" | ||
| x-on:dragover.prevent="isDragging = true" | ||
| x-on:dragenter.prevent="isDragging = true" | ||
| x-on:dragleave.prevent="isDragging = false" | ||
| x-on:drop="isDragging = false" | ||
| x-bind:class="isDragging ? 'border-[var(--ca-primary)] bg-[var(--ca-primary)]/5' : 'border-[var(--ca-border)] bg-[var(--ca-surface-muted)]/40'" | ||
| class="flex flex-col items-center justify-center gap-4 rounded-[var(--ca-radius-md)] border-2 border-dashed px-6 py-12 text-center transition-colors"> | ||
| <div class="flex h-14 w-14 items-center justify-center rounded-full bg-[var(--ca-surface-muted)] text-[var(--ca-muted)]"> | ||
| <svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 7.5m0 0L7.5 12M12 7.5V18" /> | ||
| </svg> | ||
| </div> | ||
|
|
||
| <div class="flex flex-col gap-1"> | ||
| <p class="text-base font-semibold text-[var(--ca-text)]">Drop your file here</p> | ||
| <p class="text-sm text-[var(--ca-muted)]">PNG, JPG, WEBP and PDF supported in beta</p> | ||
| </div> | ||
|
|
||
| <label class="cursor-pointer"> | ||
| <input type="file" wire:model="upload" wire:loading.attr="disabled" wire:target="upload,storeUpload" class="sr-only"> |
There was a problem hiding this comment.
The dropzone does not handle dropped files.
x-on:drop only flips the hover state, so dropping a file neither uploads it nor selects it for the hidden input. Because the drop event is not prevented, browsers can also try to open the file and navigate away from the dashboard.
Either wire DataTransfer.files into the file input/Livewire upload path or remove the “Drop your file here” affordance until drop is actually supported.
Possible fix
- <div
+ <div
x-data="{ isDragging: false }"
x-on:dragover.prevent="isDragging = true"
x-on:dragenter.prevent="isDragging = true"
x-on:dragleave.prevent="isDragging = false"
- x-on:drop="isDragging = false"
+ x-on:drop.prevent="
+ isDragging = false;
+ const files = $event.dataTransfer?.files;
+ if (files?.length) {
+ $refs.upload.files = files;
+ $refs.upload.dispatchEvent(new Event('change', { bubbles: true }));
+ }
+ "
x-bind:class="isDragging ? 'border-[var(--ca-primary)] bg-[var(--ca-primary)]/5' : 'border-[var(--ca-border)] bg-[var(--ca-surface-muted)]/40'"
class="flex flex-col items-center justify-center gap-4 rounded-[var(--ca-radius-md)] border-2 border-dashed px-6 py-12 text-center transition-colors">
...
- <input type="file" wire:model="upload" wire:loading.attr="disabled" wire:target="upload,storeUpload" class="sr-only">
+ <input x-ref="upload" type="file" wire:model="upload" wire:loading.attr="disabled" wire:target="upload,storeUpload" class="sr-only">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@resources/views/livewire/dashboard/dashboard-converter.blade.php` around
lines 5 - 25, The dropzone currently only toggles isDragging on x-on:drop and
doesn't prevent default or transfer files to the hidden file input
(wire:model="upload"), so dropped files won't upload and the browser may
navigate away; update the x-on:drop handler on the drop container to
preventDefault/stopPropagation, extract files from event.dataTransfer.files, and
programmatically assign them to the hidden input bound by wire:model="upload"
(or call Livewire.upload/dispatch a custom event to invoke the Livewire upload
handler such as storeUpload) so the dropped files are selected and processed
just like a normal file input; ensure the x-on:dragover and x-on:drop handlers
both call event.preventDefault() to stop default navigation.
Summary
Phase 6 ships the first real user flow on the dashboard:
empty upload state → upload valid file → store via
StoreUploadedFileAction→ uploaded file summary → format step placeholder.Includes CONV-071 … CONV-083:
StoreUploadedFileActionTest plan
composer test(174 passed)composer lintnpm run buildphp artisan migrate:freshSummary by cubic
Ships Phase 06: the first end-to-end dashboard upload flow. Users can upload a file, we store it, show a summary, and move to a format step placeholder.
Written for commit fcaab6c. Summary will update on new commits.
Summary by CodeRabbit