Skip to content

Commit 9080cac

Browse files
committed
Puppy Update Frontend
1 parent 8cc3b04 commit 9080cac

File tree

7 files changed

+119
-2
lines changed

7 files changed

+119
-2
lines changed

app/Http/Controllers/PuppyController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,12 @@ public function destroy(Request $request, Puppy $puppy)
123123
->route('home', ['page' => 1])
124124
->with('success', 'Puppy deleted successfully!');
125125
}
126+
127+
// ------------------------------
128+
// Update
129+
// ------------------------------
130+
public function update(Request $request, Puppy $puppy)
131+
{
132+
dd('Hello from the update method!');
133+
}
126134
}

app/Http/Resources/PuppyResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function toArray(Request $request): array
2525
'user' => UserResource::make($this->whenLoaded('user')),
2626
'can' => [
2727
'delete' => $request->user()?->can('delete', $this->resource) ?? false,
28+
'update' => $request->user()?->can('update', $this->resource) ?? false,
2829
]
2930
];
3031
}

app/Policies/PuppyPolicy.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function create(User $user): bool
3737
*/
3838
public function update(User $user, Puppy $puppy): bool
3939
{
40-
return false;
40+
return $puppy->user_id === $user->id;
4141
}
4242

4343
/**
@@ -63,4 +63,9 @@ public function forceDelete(User $user, Puppy $puppy): bool
6363
{
6464
return false;
6565
}
66+
67+
protected function isOwner(User $user, Puppy $puppy): bool
68+
{
69+
return $puppy->user_id === $user->id;
70+
}
6671
}

resources/js/components/PuppiesList.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { PaginatedResponse, type Puppy } from '../types';
22
import { LikeToggle } from './LikeToggle';
33
import { Pagination } from './pagination';
44
import { PuppyDelete } from './puppy-delete';
5+
import { PuppyUpdate } from './puppy-update';
56

67
export function PuppiesList({ puppies }: { puppies: PaginatedResponse<Puppy> }) {
78
return (
@@ -23,7 +24,15 @@ type PuppyCardProps = {
2324
function PuppyCard({ puppy }: PuppyCardProps) {
2425
return (
2526
<li key={puppy.id} className="relative overflow-clip rounded-lg bg-white shadow-md ring ring-black/5 hover:-translate-y-0.5">
26-
<div className="absolute top-2 right-2">{puppy.can.delete && <PuppyDelete puppy={puppy} />}</div>
27+
{/* Only for puppy owners */}
28+
{puppy.can.update && (
29+
<div className="absolute top-2 right-2">
30+
<div className="flex items-center gap-2">
31+
<PuppyUpdate puppy={puppy} />
32+
<PuppyDelete puppy={puppy} />
33+
</div>
34+
</div>
35+
)}
2736
<img className="aspect-square object-cover" alt={puppy.name} src={puppy.imageUrl} />
2837
<div className="gap flex items-center justify-between p-4 text-sm">
2938
<div className="flex items-center gap-2">
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
2+
import { Puppy } from '@/types';
3+
import { useForm } from '@inertiajs/react';
4+
import clsx from 'clsx';
5+
import { EditIcon, LoaderCircle } from 'lucide-react';
6+
import { useState } from 'react';
7+
import { Button } from './ui/button';
8+
import { Input } from './ui/input';
9+
import { Label } from './ui/label';
10+
11+
export function PuppyUpdate({ puppy }: { puppy: Puppy }) {
12+
const { data, setData, errors, put, processing } = useForm({
13+
name: puppy.name,
14+
trait: puppy.trait,
15+
image: null as File | null,
16+
});
17+
18+
const [open, setOpen] = useState(false);
19+
20+
return (
21+
<Dialog open={open} onOpenChange={setOpen}>
22+
<DialogTrigger asChild>
23+
<Button className="group/update bg-background/30 hover:bg-background" size="icon" variant="secondary" aria-label="Update puppy">
24+
<EditIcon className="size-4" />
25+
</Button>
26+
</DialogTrigger>
27+
<DialogContent>
28+
<DialogTitle>Edit {puppy.name}</DialogTitle>
29+
<DialogDescription>Make changes to your puppy’s information below.</DialogDescription>
30+
<form
31+
className="space-y-6"
32+
onSubmit={(e) => {
33+
e.preventDefault();
34+
// TODO
35+
put(route('puppies.update', puppy.id));
36+
}}
37+
>
38+
<Label htmlFor="name">Name</Label>
39+
<Input
40+
id="name"
41+
className="mt-1 block w-full"
42+
value={data.name}
43+
onChange={(e) => setData('name', e.target.value)}
44+
required
45+
autoComplete="name"
46+
placeholder="Full name"
47+
/>
48+
{errors.name && <p className="mt-1 text-xs text-red-500">{errors.name}</p>}
49+
50+
<Label htmlFor="trait">Personality trait</Label>
51+
<Input
52+
id="trait"
53+
className="mt-1 block w-full"
54+
value={data.trait}
55+
onChange={(e) => setData('trait', e.target.value)}
56+
required
57+
placeholder="Personality trait"
58+
/>
59+
60+
{errors.trait && <p className="mt-1 text-xs text-red-500">{errors.trait}</p>}
61+
62+
<Label htmlFor="image">Change image</Label>
63+
<Input
64+
id="image"
65+
type="file"
66+
className="mt-1 block w-full"
67+
onChange={(e) => setData('image', e.target.files ? e.target.files[0] : null)}
68+
placeholder="Profile picture"
69+
/>
70+
71+
{errors.image && <p className="mt-1 text-xs text-red-500">{errors.image}</p>}
72+
73+
<DialogFooter className="gap-2">
74+
<DialogClose asChild>
75+
<Button variant="secondary">Cancel</Button>
76+
</DialogClose>
77+
78+
<Button className="relative disabled:opacity-100" disabled={processing} type="submit">
79+
{processing && (
80+
<div className="absolute inset-0 grid place-items-center">
81+
<LoaderCircle className="size-5 animate-spin stroke-primary-foreground" />
82+
</div>
83+
)}
84+
<span className={clsx(processing && 'invisible')}>Update</span>
85+
</Button>
86+
</DialogFooter>
87+
</form>
88+
</DialogContent>
89+
</Dialog>
90+
);
91+
}

resources/js/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface Puppy {
5656
likedBy: User['id'][];
5757
can: {
5858
delete: boolean;
59+
update: boolean;
5960
};
6061
}
6162

routes/web.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
->name('puppies.like');
1313
Route::post('puppies', [PuppyController::class, 'store'])
1414
->name('puppies.store');
15+
Route::put('puppies/{puppy}', [PuppyController::class, 'update'])
16+
->name('puppies.update');
1517
Route::delete('puppies/{puppy}', [PuppyController::class, 'destroy'])
1618
->name('puppies.destroy');
1719

0 commit comments

Comments
 (0)