Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/Http/Controllers/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller
{
//
use AuthorizesRequests;
}
26 changes: 26 additions & 0 deletions app/Http/Controllers/IdeaController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Http\Controllers;

use App\Http\Requests\StoreIdeaRequest;
use App\Http\Requests\UpdateIdeaRequest;
use App\Http\Resources\CommentResource;
use App\Http\Resources\IdeaResource;
use App\Models\Idea;
Expand Down Expand Up @@ -34,4 +35,29 @@ public function store(StoreIdeaRequest $request): RedirectResponse

return redirect()->route('dashboard')->with('status', 'Your feedback has been submitted!');
}

public function edit(Idea $idea): Response
{
$this->authorize('update', $idea);

return inertia('Ideas/Edit', [
'idea' => new IdeaResource($idea),
]);
}

public function update(UpdateIdeaRequest $request, Idea $idea): RedirectResponse
{
$idea->update($request->only(['title', 'description']));

return redirect()->route('feedback.show', $idea)->with('status', 'Your feedback has been updated!');
}

public function destroy(Idea $idea): RedirectResponse
{
$this->authorize('delete', $idea);

$idea->delete();

return redirect()->route('dashboard')->with('status', 'Your feedback has been deleted!');
}
}
24 changes: 24 additions & 0 deletions app/Http/Requests/UpdateIdeaRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UpdateIdeaRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('update', $this->route('idea'));
}

/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'description' => ['required', 'string', 'max:5000'],
];
}
}
4 changes: 4 additions & 0 deletions app/Http/Resources/IdeaResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public function toArray(Request $request): array
])->values(),
),
'is_subscribed' => $user ? $this->subscribers->contains('id', $user->id) : false,
'can' => [
'update' => $user ? $user->can('update', $this->resource) : false,
'delete' => $user ? $user->can('delete', $this->resource) : false,
],
'user' => new UserResource($this->whenLoaded('user')),
'comments_count' => $this->whenCounted('comments'),
'latest_status_update' => new IdeaStatusUpdateResource($this->whenLoaded('latestStatusUpdate')),
Expand Down
19 changes: 19 additions & 0 deletions app/Policies/IdeaPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Policies;

use App\Models\Idea;
use App\Models\User;

class IdeaPolicy
{
public function update(User $user, Idea $idea): bool
{
return $user->id === $idea->user_id;
}

public function delete(User $user, Idea $idea): bool
{
return $user->id === $idea->user_id;
}
}
60 changes: 60 additions & 0 deletions resources/js/Pages/Ideas/Edit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup>
import { useForm } from '@inertiajs/vue3';
import AppLayout from '@/Layouts/AppLayout.vue';
import AppInput from '@/Components/AppInput.vue';
import AppTextarea from '@/Components/AppTextarea.vue';
import AppButton from '@/Components/AppButton.vue';
import { show, update } from '@/routes/feedback';

const props = defineProps({
idea: {
type: Object,
required: true,
},
});

const form = useForm({
title: props.idea.title,
description: props.idea.description,
});

const submit = () => {
form.put(update.url(props.idea.id));
};
</script>

<template>
<AppLayout>
<div class="max-w-md mx-auto">
<a :href="show.url(idea.id)" class="inline-flex items-center gap-1 text-xs uppercase tracking-wider text-neutral-400 hover:text-neutral-900 transition-colors mb-6">
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
Back to feedback
</a>

<h1 class="text-2xl font-semibold tracking-tight text-neutral-900 mb-8 text-center">Edit Feedback</h1>

<form class="rounded-none border border-black/[0.06] bg-white p-6 space-y-5" @submit.prevent="submit">
<AppInput
id="title"
v-model="form.title"
label="Title"
:error="form.errors.title"
/>

<AppTextarea
id="description"
v-model="form.description"
label="Description"
:rows="5"
:error="form.errors.description"
/>

<AppButton type="submit" :disabled="form.processing" class="w-full">
Save Changes
</AppButton>
</form>
</div>
</AppLayout>
</template>
28 changes: 28 additions & 0 deletions resources/js/Pages/Ideas/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CommentForm from '@/Components/CommentForm.vue';
import { dashboard } from '@/routes';
import { store } from '@/actions/App/Http/Controllers/CommentController';
import { store as subscribe, destroy as unsubscribe } from '@/routes/account/notifications';
import { edit, destroy } from '@/routes/feedback';

const props = defineProps({
idea: {
Expand All @@ -26,6 +27,14 @@ const user = computed(() => usePage().props.auth?.user ?? null);

const subscriptionForm = useForm({});

const deleteForm = useForm({});

const deleteIdea = () => {
if (window.confirm('Are you sure you want to delete this feedback? This cannot be undone.')) {
deleteForm.delete(destroy.url(props.idea.id));
}
};

const toggleSubscription = () => {
if (props.idea.is_subscribed) {
subscriptionForm.delete(unsubscribe.url(props.idea.id), { preserveScroll: true });
Expand Down Expand Up @@ -62,6 +71,25 @@ const formatDate = (dateString) => {
Submitted by {{ idea.user.name }} on {{ formatDate(idea.created_at) }}
</p>

<div v-if="idea.can?.update || idea.can?.delete" class="flex items-center gap-4 mb-6">
<a
v-if="idea.can?.update"
:href="edit.url(idea.id)"
class="text-xs uppercase tracking-wider text-neutral-400 hover:text-neutral-900 transition-colors"
>
Edit
</a>
<button
v-if="idea.can?.delete"
type="button"
class="text-xs uppercase tracking-wider text-red-400 hover:text-red-600 transition-colors disabled:opacity-50"
:disabled="deleteForm.processing"
@click="deleteIdea"
>
Delete
</button>
</div>

<div v-if="idea.latest_status_update?.message" class="mb-6 rounded-none border-l-2 border-neutral-900 bg-neutral-50 p-4">
<p class="text-sm text-neutral-700 whitespace-pre-line">{{ idea.latest_status_update.message }}</p>
<p class="mt-2 text-xs uppercase tracking-wider text-neutral-400">
Expand Down
Loading
Loading