Skip to content
Closed
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
9 changes: 9 additions & 0 deletions app/Models/Idea.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Idea extends Model
protected $fillable = [
'title',
'description',
'merged_into_id',
];

/**
Expand All @@ -41,6 +42,14 @@ public function user(): BelongsTo
return $this->belongsTo(User::class);
}

/**
* @return BelongsTo<Idea, $this>
*/
public function mergedInto(): BelongsTo
{
return $this->belongsTo(Idea::class, 'merged_into_id');
}

/**
* @return BelongsToMany<User, $this>
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Actions\Votes;
namespace App\Modules\Feedback\Actions;

use App\Models\Idea;
use App\Models\User;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Controllers\Internal;
namespace App\Modules\Feedback\Http\Controllers;

use App\Actions\Comments\PostComment;
use App\Http\Controllers\Controller;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Feedback\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\IdeaResource;
use App\Models\Idea;
use Inertia\Response;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Feedback\Http\Controllers;

use App\Http\Requests\StoreIdeaRequest;
use App\Http\Requests\UpdateIdeaRequest;
use App\Http\Controllers\Controller;
use App\Http\Resources\CommentResource;
use App\Http\Resources\IdeaResource;
use App\Models\Idea;
use App\Modules\Feedback\Http\Requests\StoreIdeaRequest;
use App\Modules\Feedback\Http\Requests\UpdateIdeaRequest;
use Illuminate\Http\RedirectResponse;
use Inertia\Response;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Feedback\Http\Controllers;

use App\Http\Requests\StoreReactionRequest;
use App\Http\Controllers\Controller;
use App\Models\Idea;
use App\Modules\Feedback\Http\Requests\StoreReactionRequest;
use Illuminate\Http\RedirectResponse;

class ReactionController extends Controller
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Feedback\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\IdeaResource;
use App\Models\Idea;
use App\Models\User;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Feedback\Http\Controllers;

use App\Actions\Votes\ToggleVote;
use App\Http\Controllers\Controller;
use App\Models\Idea;
use App\Modules\Feedback\Actions\ToggleVote;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Requests;
namespace App\Modules\Feedback\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Requests;
namespace App\Modules\Feedback\Http\Requests;

use App\Models\Reaction;
use Illuminate\Foundation\Http\FormRequest;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Requests;
namespace App\Modules\Feedback\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

Expand Down
13 changes: 13 additions & 0 deletions app/Modules/Feedback/Providers/FeedbackServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Modules\Feedback\Providers;

use Illuminate\Support\ServiceProvider;

class FeedbackServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../Routes/web.php');
}
}
32 changes: 32 additions & 0 deletions app/Modules/Feedback/Routes/web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use App\Modules\Feedback\Http\Controllers\CommentController;
use App\Modules\Feedback\Http\Controllers\DashboardController;
use App\Modules\Feedback\Http\Controllers\IdeaController;
use App\Modules\Feedback\Http\Controllers\ReactionController;
use App\Modules\Feedback\Http\Controllers\UnsubscribeController;
use App\Modules\Feedback\Http\Controllers\VoteController;
use Illuminate\Support\Facades\Route;

Route::middleware('web')->group(function () {
Route::get('/dashboard', DashboardController::class)->name('dashboard');

Route::middleware('auth')->group(function () {
Route::get('/feedback/create', [IdeaController::class, 'create'])->name('feedback.create');
Route::post('/feedback', [IdeaController::class, 'store'])->name('feedback.store');

Route::get('/feedback/{idea}/edit', [IdeaController::class, 'edit'])->name('feedback.edit');
Route::put('/feedback/{idea}', [IdeaController::class, 'update'])->name('feedback.update');
Route::delete('/feedback/{idea}', [IdeaController::class, 'destroy'])->name('feedback.destroy');

Route::post('/feedback/{idea}/vote', VoteController::class)->name('feedback.vote');
Route::post('/feedback/{idea}/reactions', ReactionController::class)->name('feedback.react');
Route::post('/feedback/{idea}/comments', [CommentController::class, 'store'])->name('feedback.comments.store');
});

Route::get('/feedback/{idea}', [IdeaController::class, 'show'])->name('feedback.show');

Route::get('/feedback/{idea}/unsubscribe/{user}', UnsubscribeController::class)
->middleware('signed')
->name('feedback.unsubscribe');
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?php

namespace App\Http\Controllers;
namespace App\Modules\Internal\Http\Controllers;

use App\Actions\Comments\PostComment;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreCommentRequest;
use App\Models\Idea;
use Illuminate\Http\RedirectResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Controllers\Internal;
namespace App\Modules\Internal\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\IdeaResource;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Controllers\Internal;
namespace App\Modules\Internal\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Resources\CommentResource;
Expand All @@ -16,6 +16,9 @@ public function show(Idea $idea): Response

return inertia('Internal/Ideas/Show', [
'idea' => new IdeaResource($idea),
'mergeTargets' => Idea::where('id', '!=', $idea->id)
->orderBy('title')
->get(['id', 'title']),
'comments' => CommentResource::collection(
$idea->publicComments()->with('user')->oldest()->get()
),
Expand Down
32 changes: 32 additions & 0 deletions app/Modules/Internal/Http/Controllers/IdeaMergeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Modules\Internal\Http\Controllers;

use App\Enums\IdeaStatus;
use App\Http\Controllers\Controller;
use App\Models\Idea;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class IdeaMergeController extends Controller
{
public function store(Request $request, Idea $idea): RedirectResponse
{
$target = Idea::find($request->input('target_id'));

$idea->comments()->update(['idea_id' => $target->id]);
$idea->reactions()->update(['idea_id' => $target->id]);

foreach ($idea->voters as $voter) {
$target->voters()->attach($voter->id);
}

$idea->merged_into_id = $target->id;
$idea->status = IdeaStatus::Declined;
$idea->save();

return redirect()
->route('internal.ideas.show', $target)
->with('status', 'Idea merged into '.$target->title.'.');
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<?php

namespace App\Http\Controllers\Internal;
namespace App\Modules\Internal\Http\Controllers;

use App\Enums\IdeaStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\UpdateIdeaStatusRequest;
use App\Models\Idea;
use App\Models\IdeaStatusUpdate;
use App\Modules\Internal\Http\Requests\UpdateIdeaStatusRequest;
use App\Notifications\IdeaStatusChanged;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?php

namespace App\Http\Controllers\Internal;
namespace App\Modules\Internal\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\StoreInternalNoteRequest;
use App\Models\Comment;
use App\Models\Idea;
use App\Modules\Internal\Http\Requests\StoreInternalNoteRequest;
use Illuminate\Http\RedirectResponse;

class NoteController extends Controller
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Middleware;
namespace App\Modules\Internal\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Requests;
namespace App\Modules\Internal\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace App\Http\Requests;
namespace App\Modules\Internal\Http\Requests;

use App\Enums\IdeaStatus;
use Illuminate\Foundation\Http\FormRequest;
Expand Down
13 changes: 13 additions & 0 deletions app/Modules/Internal/Providers/InternalServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Modules\Internal\Providers;

use Illuminate\Support\ServiceProvider;

class InternalServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../Routes/web.php');
}
}
20 changes: 20 additions & 0 deletions app/Modules/Internal/Routes/web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

use App\Modules\Internal\Http\Controllers\CommentController;
use App\Modules\Internal\Http\Controllers\IdeaDashboardController;
use App\Modules\Internal\Http\Controllers\IdeaDetailController;
use App\Modules\Internal\Http\Controllers\IdeaMergeController;
use App\Modules\Internal\Http\Controllers\IdeaStatusController;
use App\Modules\Internal\Http\Controllers\NoteController;
use Illuminate\Support\Facades\Route;

Route::middleware('web')->group(function () {
Route::prefix('internal')->middleware(['auth', 'team'])->name('internal.')->group(function () {
Route::get('/', IdeaDashboardController::class)->name('ideas.index');
Route::get('/ideas/{idea}', [IdeaDetailController::class, 'show'])->name('ideas.show');
Route::post('/ideas/{idea}/comments', [CommentController::class, 'store'])->name('ideas.comments.store');
Route::post('/ideas/{idea}/notes', [NoteController::class, 'store'])->name('ideas.notes.store');
Route::patch('/ideas/{idea}/status', [IdeaStatusController::class, 'update'])->name('ideas.status.update');
Route::post('/ideas/{idea}/merge', [IdeaMergeController::class, 'store'])->name('ideas.merge.store');
});
});
2 changes: 1 addition & 1 deletion bootstrap/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
]);

$middleware->alias([
'team' => \App\Http\Middleware\EnsureTeamMember::class,
'team' => \App\Modules\Internal\Http\Middleware\EnsureTeamMember::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
Expand Down
2 changes: 2 additions & 0 deletions bootstrap/providers.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

return [
App\Modules\Feedback\Providers\FeedbackServiceProvider::class,
App\Modules\Internal\Providers\InternalServiceProvider::class,
App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('ideas', function (Blueprint $table) {
$table->foreignId('merged_into_id')->nullable()->constrained('ideas')->nullOnDelete();
});
}

public function down(): void
{
Schema::table('ideas', function (Blueprint $table) {
$table->dropConstrainedForeignId('merged_into_id');
});
}
};
2 changes: 2 additions & 0 deletions docs/feedback.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Signed-in users create feedback from the `Ideas/Create` page.

New ideas start with the default status defined by the database (see [Internal team dashboard](internal-dashboard.md) for the status pipeline).

If an idea turns out to be a duplicate, a team member can merge it into the idea being kept, which moves its comments, reactions, and votes across and closes the duplicate as Declined. See [Merging duplicate ideas](internal-dashboard.md#merging-duplicate-ideas).

## Viewing feedback

`GET /feedback/{idea}` (`feedback.show`) renders the `Ideas/Show` page. This route is public — anyone, signed in or not, can view an idea. The page includes:
Expand Down
Loading
Loading