Langsung ke konten

06. Menghapus Chirp

Terkadang mengedit saja tidak cukup untuk memperbaiki sebuah pesan, jadi mari kita beri kemampuan kepada pengguna untuk menghapus Chirp mereka.

Semoga Anda sudah mulai terbiasa sekarang. Kami yakin Anda akan terkesan betapa cepatnya kita bisa menambahkan fitur ini.

Routing

Kita akan mulai lagi dengan memperbarui rute kita untuk mengaktifkan rute chirps.destroy:

routes/web.php
<?php
 ...
use App\Http\Controllers\ChirpController;
use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
 
Route::get('/', function () {
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
});
 
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
 
Route::resource('chirps', ChirpController::class)
- ->only(['index', 'store', 'update'])
+ ->only(['index', 'store', 'update', 'destroy'])
->middleware(['auth', 'verified']);
 ...
require __DIR__.'/auth.php';
 

Tabel rute untuk controller ini sekarang terlihat seperti ini:

Verb URI Action Nama Rute
GET /chirps index chirps.index
POST /chirps store chirps.store
PUT/PATCH /chirps/{chirp} update chirps.update
DELETE /chirps/{chirp} destroy chirps.destroy

Memperbarui controller

Sekarang kita dapat memperbarui metode destroy pada kelas ChirpController untuk melakukan penghapusan dan kembali ke indeks Chirp:

app/Http/Controllers/ChirpController.php
<?php
 ...
namespace App\Http\Controllers;
 
use App\Models\Chirp;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Inertia\Inertia;
use Inertia\Response;
 
class ChirpController extends Controller
{
 ...
/**
* Menampilkan daftar resource.
*/
public function index(): Response
{
return Inertia::render('Chirps/Index', [
'chirps' => Chirp::with('user:id,name')->latest()->get(),
]);
}
 
/**
* Menampilkan formulir untuk membuat resource baru.
*/
public function create()
{
//
}
 
/**
* Menyimpan resource yang baru dibuat ke dalam penyimpanan.
*/
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'message' => 'required|string|max:255',
]);
 
$request->user()->chirps()->create($validated);
 
return redirect(route('chirps.index'));
}
 
/**
* Menampilkan resource yang dipilih/spesifik.
*/
public function show(Chirp $chirp)
{
//
}
 
/**
* Menampilkan formulir untuk mengedit resource yang dipilih/spesifik.
*/
public function edit(Chirp $chirp)
{
//
}
 
/**
* Memperbarui resource yang dipilih/spesifik di dalam penyimpanan.
*/
public function update(Request $request, Chirp $chirp): RedirectResponse
{
Gate::authorize('update', $chirp);
 
$validated = $request->validate([
'message' => 'required|string|max:255',
]);
 
$chirp->update($validated);
 
return redirect(route('chirps.index'));
}
 
/**
* Menghapus resource yang dipilih/spesifik dari penyimpanan.
*/
- public function destroy(Chirp $chirp)
+ public function destroy(Chirp $chirp): RedirectResponse
{
- //
+ Gate::authorize('delete', $chirp);
+ 
+ $chirp->delete();
+ 
+ return redirect(route('chirps.index'));
}
}

Otorisasi

Sama seperti saat mengedit, kita hanya ingin penulis Chirp yang dapat menghapus Chirp mereka, jadi mari perbarui metode delete pada kelas ChirpPolicy kita:

app/Policies/ChirpPolicy.php
<?php
 ...
namespace App\Policies;
 
use App\Models\Chirp;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
 
class ChirpPolicy
{
 ...
use HandlesAuthorization;
 
/**
* Tentukan apakah pengguna dapat melihat model apa saja.
*/
public function viewAny(User $user): bool
{
//
}
 
/**
* Tentukan apakah pengguna dapat melihat model yang ditentukan.
*/
public function view(User $user, Chirp $chirp): bool
{
//
}
 
/**
* Tentukan apakah pengguna dapat membuat model.
*/
public function create(User $user): bool
{
//
}
 
/**
* Menentukan apakah pengguna dapat memperbarui model.
*/
public function update(User $user, Chirp $chirp): bool
{
return $chirp->user()->is($user);
}
 
/**
* Menentukan apakah pengguna dapat menghapus model.
*/
public function delete(User $user, Chirp $chirp): bool
{
- //
+ return $this->update($user, $chirp);
}
 ...
/**
* Menentukan apakah pengguna dapat merestorasi model.
*/
public function restore(User $user, Chirp $chirp): bool
{
//
}
 
/**
* Menentukan apakah pengguna dapat menghapus model secara permanen.
*/
public function forceDelete(User $user, Chirp $chirp): bool
{
//
}
 
}

Alih-alih mengulang logika dari metode update, kita dapat menentukan logika yang sama dengan memanggil metode update dari metode delete kita. Siapa pun yang berwenang untuk memperbarui Chirp sekarang juga akan berwenang untuk menghapusnya.

Memperbarui komponen

Terakhir, kita dapat menambahkan tombol hapus ke menu dropdown yang kita buat sebelumnya di komponen Chirp kita:

resources/js/Components/Chirp.vue
<script setup>
import Dropdown from '@/Components/Dropdown.vue';
+import DropdownLink from '@/Components/DropdownLink.vue';
import InputError from '@/Components/InputError.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
 ...
dayjs.extend(relativeTime);
 
const props = defineProps(['chirp']);
 
const form = useForm({
message: props.chirp.message,
});
 
const editing = ref(false);
 
</script>
 
<template>
<div class="p-6 flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div class="flex-1">
<div class="flex justify-between items-center">
<div>
<span class="text-gray-800">{{ chirp.user.name }}</span>
<small class="ml-2 text-sm text-gray-600">{{ dayjs(chirp.created_at).fromNow() }}</small>
<small v-if="chirp.created_at !== chirp.updated_at" class="text-sm text-gray-600"> &middot; edited</small>
</div>
<Dropdown v-if="chirp.user.id === $page.props.auth.user.id">
<template #trigger>
<button>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
</svg>
</button>
</template>
<template #content>
<button class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out" @click="editing = true">
Edit
</button>
+ <DropdownLink as="button" :href="route('chirps.destroy', chirp.id)" method="delete">
+ Delete
+ </DropdownLink>
</template>
</Dropdown>
</div>
<form v-if="editing" @submit.prevent="form.put(route('chirps.update', chirp.id), { onSuccess: () => editing = false })">
<textarea v-model="form.message" class="mt-4 w-full text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>
<InputError :message="form.errors.message" class="mt-2" />
<div class="space-x-2">
<PrimaryButton class="mt-4">Save</PrimaryButton>
<button class="mt-4" @click="editing = false; form.reset(); form.clearErrors()">Cancel</button>
</div>
</form>
<p v-else class="mt-4 text-lg text-gray-900">{{ chirp.message }}</p>
</div>
</div>
</template>
resources/js/Components/Chirp.jsx
import React, { useState } from 'react';
import Dropdown from '@/Components/Dropdown';
import InputError from '@/Components/InputError';
import PrimaryButton from '@/Components/PrimaryButton';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useForm, usePage } from '@inertiajs/react';
 
dayjs.extend(relativeTime);
 
export default function Chirp({ chirp }) {
const { auth } = usePage().props;
 
const [editing, setEditing] = useState(false);
 
const { data, setData, patch, processing, reset, errors } = useForm({
message: chirp.message,
});
 
const submit = (e) => {
e.preventDefault();
patch(route('chirps.update', chirp.id), { onSuccess: () => setEditing(false) });
};
 
return (
<div className="p-6 flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-gray-600 -scale-x-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<div className="flex-1">
<div className="flex justify-between items-center">
<div>
<span className="text-gray-800">{chirp.user.name}</span>
<small className="ml-2 text-sm text-gray-600">{dayjs(chirp.created_at).fromNow()}</small>
{ chirp.created_at !== chirp.updated_at && <small className="text-sm text-gray-600"> &middot; edited</small>}
</div>
{chirp.user.id === auth.user.id &&
<Dropdown>
<Dropdown.Trigger>
<button>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />
</svg>
</button>
</Dropdown.Trigger>
<Dropdown.Content>
<button className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out" onClick={() => setEditing(true)}>
Edit
</button>
+ <Dropdown.Link as="button" href={route('chirps.destroy', chirp.id)} method="delete">
+ Delete
+ </Dropdown.Link>
</Dropdown.Content>
</Dropdown>
}
</div>
{editing
? <form onSubmit={submit}>
<textarea value={data.message} onChange={e => setData('message', e.target.value)} className="mt-4 w-full text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>
<InputError message={errors.message} className="mt-2" />
<div className="space-x-2">
<PrimaryButton className="mt-4">Save</PrimaryButton>
<button className="mt-4" onClick={() => setEditing(false) && reset()}>Cancel</button>
</div>
</form>
: <p className="mt-4 text-lg text-gray-900">{chirp.message}</p>
}
</div>
</div>
)
}

Coba sekarang

Jika Anda membuat Chirp yang tidak Anda sukai, cobalah untuk menghapusnya!

Menghapus chirp

Lanjutkan ke notifikasi & event...