MerddinEmrys liked a comment+100 XP
6d ago
The issue was that the uploaded image was saved in storage/app/public, but Laravel serves files from the public/ directory.
Controller / Route
Route::post('/puppies', function (Request $request) {
$validated = $request->validate([
'name' => 'required|string',
'trait' => 'required|string',
'image_url' => 'required|mimes:jpg,jpeg,png|max:2048',
]);
$path = $request->file('image_url')->store('images', 'public');
$puppy = Puppy::create([
...$validated,
'image_url' => $path,
]);
return new PuppyResource($puppy);
});
Resource
use Illuminate\Support\Facades\Storage;
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'trait' => $this->trait,
'imageUrl' => url(Storage::url($this->image_url)),
'likedBy' => $this->likedBy->pluck('id'),
];
}
⚡ Result
Now your React frontend receives:
{
"data": {
"id": 1,
"name": "Tommy",
"trait": "Loves running",
"imageUrl": "http://127.0.0.1:8000/storage/images/abc123.png",
"likedBy": []
}
}
MerddinEmrys liked a comment+100 XP
1w ago
@puck_hb Hey!
I think I ommitted this in the video because I have TypeScript installed globally.
You can fix this by installing it as a dev dependency:
npm install --save-dev typescript
This will give you access to the tsc command locally (which is what npm run check relies on). After that, npm run check should work as expected!
MerddinEmrys liked a comment+100 XP
1w ago
@Maruu I think the brief delay in updating the URL is what's causing the issue. This test is functioning correctly with a manual 100ms pause inserted ...
it('can open a modal and close it', function () {
$this->browse(function (Browser $browser) {
$user = User::orderBy('name')->firstOrFail();
$browser
->loginAs(1)
->visit(route('users.index'))
->waitFor('table')
->press('@edit-user-'.$user->id)
->waitFor('@modal-content')
->assertUrlIs(route('users.edit', $user->id))
->within('@modal-content', function(Browser $browser) {
$browser->press('CANCEL');
})
->waitUntilMissing('@modal-wrapper')
->pause(100)
->assertUrlIs(route('users.index'));
});
});
MerddinEmrys liked a comment+100 XP
2w ago
Great series, hope you do more in the future.
In relation to this episode now Scout is installed you can try other drivers. For example, https://github.com/teamtnt/laravel-scout-tntsearch-driver is very easy to setup and uses a local .sqlite database. That will give you fuzzy searching and searches where the spelling is incorrect in a similar manner to Meilsearch.
I'd say Meilsearch is definitely better if you have the facility /access to install it on your hosting. However, TNT search works anywhere, even on shared hosting.
The other point this lesson didn't touch on too much was speed. Like queries in MySQL is slow and will become slower as your database increases in size. Scout drivers (certainly TNT search and Meilsearch) are lighting fast. IN the video note the response times in the Meilsearch dashboard - 9ms. Insane.
As an example, I have a site uses TNT Search, that has indexed 1.4 million products. I get search results within 80ms, where I've indexed 9 columns of my Eloquent model.
The fastest driver I've come across in Agolia, for a hosted solution I have no idea how they return results so fast.
I digress :)
MerddinEmrys liked a comment+100 XP
3w ago
Thank you @MerddinEmrys
I had problems running npm run build and had to change
form.css
from
@media (min-width: var(--breakpoint-md)) {
to be
@media (min-width: 48rem) {
MerddinEmrys liked a comment+100 XP
3w ago
MerddinEmrys liked a comment+100 XP
1mo ago
MerddinEmrys liked a comment+100 XP
1mo ago
Color theme for those looking to copy:
--color-background: oklch(0.12 0 0);
--color-foreground: oklch(0.95 0 0);
--color-card: oklch(0.16 0 0);
--color-primary: oklch(0.65 0.15 160);
--color-primary-foreground: oklch(0.12 0 0);
--color-border: oklch(0.24 0 0);
--color-input: oklch(0.24 0 0);
--color-muted-foreground: oklch(0.6 0 0);
MerddinEmrys liked a comment+100 XP
1mo ago
MerddinEmrys liked a comment+100 XP
1mo ago
MerddinEmrys wrote a comment+100 XP
3mos ago
Here are the full CSS component files for those looking to copy:
resources/css/components/btn.css
button {
cursor: pointer;
}
.btn {
background: var(--color-primary);
border-radius: var(--radius-xl);
color: var(--color-primary-foreground);
padding-inline: calc(var(--spacing) * 3);
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
cursor: pointer;
height: calc(var(--spacing) * 8);
line-height: calc(var(--spacing) * 8);
display: inline-block;
}
.btn.btn-outlined {
background: transparent;
border: 1px solid var(--color-border);
border-color: var(--color-border);
color: var(--color-foreground);
}
.btn.btn-outlined:hover {
background: color-mix(in srgb, black 25%, var(--color-input));
}
.btn.btn-ghost {
background: transparent;
}
.btn:has(> svg) {
display: flex;
align-items: center;
column-gap: calc(var(--spacing) * 2);
}
.btn:hover {
background: color-mix(in srgb, black 10%, var(--color-primary));
text-decoration: none;
}
resources/css/components/form.css
label {
color: var(--color-foreground);
}
.input {
border-radius: var(--radius-md);
height: calc(var(--spacing) * 10);
width: 100%;
border-width: 1px;
border-color: var(--color-border);
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
background-color: var(--color-card);
color: var(--color-foreground);
outline: 2px solid transparent;
outline-offset: 2px;
}
.input::placeholder {
color: var(--color-muted-foreground);
}
.input:focus-visible {
outline: 0;
box-shadow:
0 0 0 calc(var(--spacing) * .5) var(--color-background)
0 0 0 calc(var(--spacing) * 1) var(--color-primary);
}
@media (min-width: var(--breakpoint-md)) {
.input {
font-size: var(--text-sm);
}
}
.textarea {
border-radius: var(--radius-md);
height: calc(var(--spacing) * 40);
width: 100%;
border-width: 1px;
border-color: var(--color-border);
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
background-color: var(--color-card);
color: var(--color-foreground);
outline: 2px solid transparent;
outline-offset: 2px;
}
.textarea::placeholder {
color: var(--color-muted-foreground);
}
.textarea:focus-visible {
outline: 0;
box-shadow:
0 0 0 calc(var(--spacing) * .5) var(--color-background)
0 0 0 calc(var(--spacing) * 1) var(--color-primary);
}
.label {
display: block;
font-size: var(--text-sm);
}
input[type=file] {
display: block;
width: 100%;
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--color-foreground);
}
input[type=file]::file-selector-button {
margin-right: calc(var(--spacing) * 4);
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 4);
border-radius: calc(var(--spacing) * 2);
border-width: 0;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
background-color: var(--color-primary);
color: rgb(var(--color-background));
}
input[type=file]::file-selector-button:hover {
opacity: 0.9;
}
.error {
font-size: var(--text-sm);
color: var(--color-red-600);
}
.form-muted-icon {
color: var(--color-muted-foreground);
}
.form-muted-icon:hover {
color: var(--color-foreground);
}