Create page for spaces that shows invitees their invitation
ADD
app/Actions/AcceptSpaceInviteAction.php
+<?php
+
+namespace App\Actions;
+
+use App\Exceptions\SpaceInviteNotFoundException;
+use App\Models\SpaceInvite;
+use Illuminate\Support\Facades\Auth;
+
+class AcceptSpaceInviteAction
+{
+ public function execute(int $inviteId): void
+ {
+ $invite = SpaceInvite::find($inviteId);
+
+ if (!$invite) {
+ throw new SpaceInviteNotFoundException();
+ }
+
+ $invite->fill(['accepted' => true])->save();
+
+ Auth::user()->spaces()->attach($invite->space, ['role' => $invite->role]);
+ }
+}

ADD
app/Actions/DenySpaceInviteAction.php
+<?php
+
+namespace App\Actions;
+
+use App\Exceptions\SpaceInviteNotFoundException;
+use App\Models\SpaceInvite;
+
+class DenySpaceInviteAction
+{
+ public function execute(int $inviteId): void
+ {
+ $invite = SpaceInvite::find($inviteId);
+
+ if (!$invite) {
+ throw new SpaceInviteNotFoundException();
+ }
+
+ $invite->fill(['accepted' => false])->save();
+ }
+}

ADD
app/Exceptions/SpaceInviteNotFoundException.php
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class SpaceInviteNotFoundException extends Exception
+{
+ //
+}

ADD
app/Http/Controllers/SpaceInviteController.php
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Actions\AcceptSpaceInviteAction;
+use App\Actions\DenySpaceInviteAction;
+use App\Models\Space;
+use App\Models\SpaceInvite;
+use Illuminate\Http\Request;
+
+class SpaceInviteController extends Controller
+{
+ public function show(Request $request, Space $space, SpaceInvite $invite)
+ {
+ if ($invite->space->id !== $space->id) {
+ abort(404);
+ }
+
+ $this->authorize('access', $invite);
+
+ if ($invite->accepted !== null) {
+ abort(410);
+ }
+
+ return view('space_invites.show', [
+ 'invite' => $invite
+ ]);
+ }
+
+ public function accept(Request $request, Space $space, SpaceInvite $invite)
+ {
+ if ($invite->space->id !== $space->id) {
+ abort(404);
+ }
+
+ $this->authorize('access', $invite);
+
+ if ($invite->accepted !== null) {
+ abort(410);
+ }
+
+ (new AcceptSpaceInviteAction())->execute($invite->id);
+
+ return redirect()->route('settings.spaces.index');
+ }
+
+ public function deny(Request $request, Space $space, SpaceInvite $invite)
+ {
+ if ($invite->space->id !== $space->id) {
+ abort(404);
+ }
+
+ $this->authorize('access', $invite);
+
+ if ($invite->accepted !== null) {
+ abort(410);
+ }
+
+ (new DenySpaceInviteAction())->execute($invite->id);
+
+ return redirect()->route('settings.spaces.index');
+ }
+}

MODIFY
app/Models/SpaceInvite.php
app/Models/SpaceInvite.php
@@ -10,7 +10,12 @@ class SpaceInvite extends Model
'space_id',
'inviter_user_id',
'invitee_user_id',
- 'role'
+ 'role',
+ 'accepted'
+ ];
+
+ protected $casts = [
+ 'accepted' => 'boolean'
];
// Relations
@@ -28,4 +33,22 @@ class SpaceInvite extends Model
{
return $this->belongsTo(User::class, 'inviter_user_id');
}
+
+ // Accessors
+ public function getStatusAttribute(): string
+ {
+ if ($this->accepted === null) {
+ return 'Pending';
+ }
+
+ if ($this->accepted === true) {
+ return 'Accepted (' . date('d-m', strtotime($this->updated_at)) . ')';
+ }
+
+ if ($this->accepted === false) {
+ return 'Denied (' . date('d-m', strtotime($this->updated_at)) . ')';
+ }
+
+ return 'Unknown';
+ }
}

ADD
app/Policies/SpaceInvitePolicy.php
+<?php
+
+namespace App\Policies;
+
+use App\Models\SpaceInvite;
+use App\Models\User;
+use Illuminate\Auth\Access\HandlesAuthorization;
+
+class SpaceInvitePolicy
+{
+ use HandlesAuthorization;
+
+ public function access(User $user, SpaceInvite $invite)
+ {
+ return $invite->invitee_user_id === $user->id;
+ }
+}

MODIFY
app/Providers/AuthServiceProvider.php
app/Providers/AuthServiceProvider.php
@@ -15,6 +15,8 @@ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvid
use App\Policies\RecurringPolicy;
use App\Models\Recurring;
use App\Models\Space;
+use App\Models\SpaceInvite;
+use App\Policies\SpaceInvitePolicy;
use App\Policies\SpacePolicy;
class AuthServiceProvider extends ServiceProvider
@@ -28,6 +30,7 @@ class AuthServiceProvider extends ServiceProvider
'App\Model' => 'App\Policies\ModelPolicy',
Earning::class => EarningPolicy::class,
Space::class => SpacePolicy::class,
+ SpaceInvite::class => SpaceInvitePolicy::class,
Spending::class => SpendingPolicy::class,
Recurring::class => RecurringPolicy::class,
Tag::class => TagPolicy::class,

MODIFY
resources/lang/en/actions.php
resources/lang/en/actions.php
@@ -12,5 +12,8 @@ return [
'yes' => 'Yes',
'no' => 'No',
- 'invite' => 'Invite'
+ 'invite' => 'Invite',
+
+ 'accept' => 'Accept',
+ 'deny' => 'Deny'
];

MODIFY
resources/lang/nl/actions.php
resources/lang/nl/actions.php
@@ -12,5 +12,8 @@ return [
'yes' => 'Ja',
'no' => 'Nee',
- 'invite' => 'Nodig uit'
+ 'invite' => 'Nodig uit',
+
+ 'accept' => 'Accepteer',
+ 'deny' => 'Weiger'
];

ADD
resources/views/space_invites/show.blade.php
+@extends('layout')
+
+@section('title', __('general.invite'))
+
+@section('body')
+ <div class="wrapper wrapper--narrow my-3">
+ <h2>{{ __('general.invite') }}</h2>
+ <div class="box mt-3">
+ <div class="box__section">
+ <h3 class="color-dark mb-1">{{ __('general.invited_to') }} "{{ $invite->space->name }}"</h3>
+ <div>{{ __('general.sent_by') }} {{ $invite->inviter->name }}.</div>
+ <div class="row row--middle mt-2">
+ <form method="POST" action="{{ route('space_invites.accept', ['space' => $invite->space->id, 'invite' => $invite->id]) }}">
+ {{ csrf_field() }}
+ <button class="button">{{ __('actions.accept') }}</button>
+ </form>
+ <form method="POST" action="{{ route('space_invites.deny', ['space' => $invite->space->id, 'invite' => $invite->id]) }}">
+ {{ csrf_field() }}
+ <button class="button button--secondary ml-2">{{ __('actions.deny') }}</button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+@endsection

MODIFY
resources/views/spaces/edit.blade.php
resources/views/spaces/edit.blade.php
@foreach ($space->invites as $i => $invite)
<div class="{{ $i > 0 ? 'mt-2' : '' }}">
<div class="color-dark mb-1">{{ $invite->invitee->name }}</div>
- <div class="fs-sm">{{ __('general.invited_by') }} {{ $invite->inviter->name }}</div>
+ <div class="fs-sm">{{ __('general.invited_by') }} {{ $invite->inviter->name }} &middot; {{ $invite->status }}</div>
</div>
@endforeach
</div>

MODIFY
routes/web.php
routes/web.php
@@ -16,6 +16,7 @@ use App\Http\Controllers\ReportController;
use App\Http\Controllers\ResetPasswordController;
use App\Http\Controllers\SettingsController;
use App\Http\Controllers\SpaceController;
+use App\Http\Controllers\SpaceInviteController;
use App\Http\Controllers\SpendingController;
use App\Http\Controllers\TagController;
use App\Http\Controllers\TransactionController;
@@ -127,6 +128,12 @@ Route::group(['middleware' => ['auth']], function () {
Route::post('/spaces/{space}/invite', [SpaceController::class, 'invite'])->name('invite');
});
+ Route::name('space_invites.')->group(function () {
+ Route::get('/spaces/{space}/invites/{invite}', [SpaceInviteController::class, 'show'])->name('show');
+ Route::post('/spaces/{space}/invites/{invite}/accept', [SpaceInviteController::class, 'accept'])->name('accept');
+ Route::post('/spaces/{space}/invites/{invite}/deny', [SpaceInviteController::class, 'deny'])->name('deny');
+ });
+
Route::name('ideas.')->group(function () {
Route::get('/ideas/create', [IdeaController::class, 'create'])->name('create');
Route::post('/ideas', [IdeaController::class, 'store']);

Create page for spaces that shows invitees their invitation