diff --git a/app/Livewire/Concerns/NeedsVerifiedEmail.php b/app/Livewire/Concerns/NeedsVerifiedEmail.php new file mode 100644 index 000000000..b092704cb --- /dev/null +++ b/app/Livewire/Concerns/NeedsVerifiedEmail.php @@ -0,0 +1,24 @@ +user()?->hasVerifiedEmail()) { + return false; + } + + session()->flash('flash-message', 'You must verify your email address before you can continue.'); + + $this->redirectRoute('verification.notice', navigate: true); + + return true; + } +} diff --git a/app/Livewire/Questions/Create.php b/app/Livewire/Questions/Create.php index a83fd1053..d9774a85e 100644 --- a/app/Livewire/Questions/Create.php +++ b/app/Livewire/Questions/Create.php @@ -4,6 +4,7 @@ namespace App\Livewire\Questions; +use App\Livewire\Concerns\NeedsVerifiedEmail; use App\Models\Question; use App\Models\User; use App\Rules\MaxUploads; @@ -28,6 +29,7 @@ */ final class Create extends Component { + use NeedsVerifiedEmail; use WithFileUploads; /** @@ -76,6 +78,10 @@ final class Create extends Component */ public function updated(mixed $property): void { + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + if ($property === 'images') { $this->runImageValidation(); $this->uploadImages(); @@ -87,6 +93,10 @@ public function updated(mixed $property): void */ public function runImageValidation(): void { + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $this->validate( rules: [ 'images' => [ @@ -204,6 +214,10 @@ public function store(Request $request): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $user = type($request->user())->as(User::class); if (! app()->isLocal() && $user->questionsSent()->where('created_at', '>=', now()->subMinute())->count() >= 3) { diff --git a/app/Livewire/Questions/Edit.php b/app/Livewire/Questions/Edit.php index 65806fc87..df9113111 100644 --- a/app/Livewire/Questions/Edit.php +++ b/app/Livewire/Questions/Edit.php @@ -4,6 +4,7 @@ namespace App\Livewire\Questions; +use App\Livewire\Concerns\NeedsVerifiedEmail; use App\Models\Question; use App\Models\User; use App\Rules\NoBlankCharacters; @@ -14,6 +15,8 @@ final class Edit extends Component { + use NeedsVerifiedEmail; + /** * The component's question ID. */ @@ -41,6 +44,10 @@ public function mount(string $questionId): void */ public function update(Request $request): void { + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $validated = $this->validate([ 'answer' => ['required', 'string', 'max:1000', new NoBlankCharacters], ]); diff --git a/app/Livewire/Questions/Show.php b/app/Livewire/Questions/Show.php index 1f27562f9..47dcc957a 100644 --- a/app/Livewire/Questions/Show.php +++ b/app/Livewire/Questions/Show.php @@ -4,6 +4,7 @@ namespace App\Livewire\Questions; +use App\Livewire\Concerns\NeedsVerifiedEmail; use App\Models\Question; use App\Models\User; use Illuminate\Database\Eloquent\Builder; @@ -16,6 +17,8 @@ final class Show extends Component { + use NeedsVerifiedEmail; + /** * The component's question ID. */ @@ -96,6 +99,10 @@ public function ignore(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + if ($this->inIndex) { $this->dispatch('notification.created', message: 'Question ignored.'); @@ -125,6 +132,10 @@ public function bookmark(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $question = Question::findOrFail($this->questionId); $bookmark = $question->bookmarks()->firstOrCreate([ @@ -148,6 +159,10 @@ public function like(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $question = Question::findOrFail($this->questionId); $question->likes()->firstOrCreate([ @@ -166,6 +181,10 @@ public function pin(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $user = type(auth()->user())->as(User::class); $question = Question::findOrFail($this->questionId); @@ -189,6 +208,10 @@ public function unpin(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $question = Question::findOrFail($this->questionId); $this->authorize('update', $question); @@ -210,6 +233,10 @@ public function unbookmark(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $question = Question::findOrFail($this->questionId); if ($bookmark = $question->bookmarks()->where('user_id', auth()->id())->first()) { @@ -235,6 +262,10 @@ public function unlike(): void return; } + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + $question = Question::findOrFail($this->questionId); if ($like = $question->likes()->where('user_id', auth()->id())->first()) { diff --git a/tests/Unit/Livewire/Concerns/NeedsVerifiedEmailTest.php b/tests/Unit/Livewire/Concerns/NeedsVerifiedEmailTest.php new file mode 100644 index 000000000..14940082f --- /dev/null +++ b/tests/Unit/Livewire/Concerns/NeedsVerifiedEmailTest.php @@ -0,0 +1,59 @@ +create([ + 'email_verified_at' => null, + ]); + + $component = Livewire::actingAs($user)->test(myComponentNeedsVerifiedEmail()::class); + $component->call('someMethod'); + $component->assertRedirect(route('verification.notice')); + $component->assertSet('isConfirmed', false); + + expect(session('flash-message'))->toBe('You must verify your email address before you can continue.'); +}); + +it('can check if the user has a verified email address', function () { + $user = User::factory()->create(); + + $component = Livewire::actingAs($user)->test(myComponentNeedsVerifiedEmail()::class); + $component->call('someMethod'); + $component->assertSet('isConfirmed', true); + + expect(session('flash-message'))->toBeNull(); +}); + +function myComponentNeedsVerifiedEmail(): Component +{ + return new class() extends Component + { + use NeedsVerifiedEmail; + + public bool $isConfirmed = false; + + public function someMethod() + { + if ($this->doesNotHaveVerifiedEmail()) { + return; + } + + $this->isConfirmed = true; + } + + public function render() + { + return <<<'HTML' +
+ +
+ HTML; + } + }; +}