diff --git a/app/Http/Controllers/BlogController.php b/app/Http/Controllers/BlogController.php index efac56a..e97160e 100644 --- a/app/Http/Controllers/BlogController.php +++ b/app/Http/Controllers/BlogController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use App\Mail\BlogCommentNotificationMail; +use App\Mail\BlogNotificationMail; use App\Models\Blog; use App\Models\BlogComment; use Illuminate\Http\Request; @@ -97,6 +97,18 @@ public function toggleReaction(Blog $blog) $existing->delete(); } else { $blog->reactions()->create(['user_id' => $user->id]); + + $reactionsCount = $blog->reactions()->count(); + $milestones = [1, 10, 25, 50, 100, 250, 500, 1000]; + $isMilestone = in_array($reactionsCount, $milestones, true) || ($reactionsCount > 1000 && $reactionsCount % 500 === 0); + + if ($isMilestone) { + $blog->loadMissing('user:id,name,email,receive_emails'); + + if ($blog->user && $blog->user_id !== $user->id && $blog->user->email && $blog->user->receive_emails !== false) { + Mail::to($blog->user->email)->queue(BlogNotificationMail::forReactionMilestone($blog, $user, $reactionsCount)); + } + } } return back(); @@ -123,7 +135,7 @@ public function storeComment(Request $request, Blog $blog) $blog->loadMissing('user:id,name,email,receive_emails'); if ($blog->user && $blog->user_id !== $userId && $blog->user->email && $blog->user->receive_emails !== false) { - Mail::to($blog->user->email)->queue(new BlogCommentNotificationMail($blog, $comment)); + Mail::to($blog->user->email)->queue(BlogNotificationMail::forComment($blog, $comment)); } return back()->with('success', 'Comment posted successfully.'); diff --git a/app/Mail/BlogCommentNotificationMail.php b/app/Mail/BlogCommentNotificationMail.php deleted file mode 100644 index 705dcd9..0000000 --- a/app/Mail/BlogCommentNotificationMail.php +++ /dev/null @@ -1,60 +0,0 @@ -comment->user?->name ?? 'Someone'; - - return new Envelope( - subject: "New comment on your blog: \"{$this->blog->title}\"", - ); - } - - public function content(): Content - { - $appUrl = config('app.url', 'https://hscstack.site'); - $blogUrl = rtrim($appUrl, '/')."/blogs/{$this->blog->slug}#comments"; - $commenterName = htmlspecialchars($this->comment->user?->name ?? 'A reader', ENT_QUOTES, 'UTF-8'); - $commentContent = nl2br(htmlspecialchars($this->comment->content, ENT_QUOTES, 'UTF-8')); - $blogTitle = htmlspecialchars($this->blog->title, ENT_QUOTES, 'UTF-8'); - - $mailContent = "

{$commenterName} just commented on your blog post \"{$blogTitle}\":

" - .'
' - ."

{$commentContent}

" - .'
' - .'

' - ."" - .'View & Reply to Comment →' - .'' - .'

'; - - return new Content( - view: 'emails.bulk_announcement', - with: [ - 'mailSubject' => "New comment on \"{$this->blog->title}\"", - 'mailContent' => $mailContent, - 'recipientName' => $this->blog->user?->name, - 'imageUrl' => null, - ], - ); - } -} diff --git a/app/Mail/BlogNotificationMail.php b/app/Mail/BlogNotificationMail.php new file mode 100644 index 0000000..62da325 --- /dev/null +++ b/app/Mail/BlogNotificationMail.php @@ -0,0 +1,96 @@ +slug}#comments"; + $commenterName = htmlspecialchars($comment->user?->name ?? 'A reader', ENT_QUOTES, 'UTF-8'); + $commentContent = nl2br(htmlspecialchars($comment->content, ENT_QUOTES, 'UTF-8')); + $blogTitle = htmlspecialchars($blog->title, ENT_QUOTES, 'UTF-8'); + + $mailSubject = "New comment on \"{$blog->title}\""; + $mailContent = "

{$commenterName} just commented on your blog post \"{$blogTitle}\":

" + .'
' + ."

{$commentContent}

" + .'
' + .'

' + ."" + .'View & Reply to Comment →' + .'' + .'

'; + + return new self($mailSubject, $mailContent, $blog->user?->name); + } + + public static function forReactionMilestone(Blog $blog, User $reactor, int $milestoneCount): self + { + $appUrl = config('app.url', 'https://hscstack.site'); + $blogUrl = rtrim($appUrl, '/')."/blogs/{$blog->slug}"; + $reactorName = htmlspecialchars($reactor->name, ENT_QUOTES, 'UTF-8'); + $blogTitle = htmlspecialchars($blog->title, ENT_QUOTES, 'UTF-8'); + + if ($milestoneCount === 1) { + $mailSubject = 'Your blog received its first love reaction! ❤️'; + $headline = 'Congratulations! Your blog post received its very first love reaction ❤️'; + $message = "

{$reactorName} just loved your article \"{$blogTitle}\".

" + .'

Readers are appreciating your work! Keep sharing knowledge with the community.

'; + } else { + $mailSubject = "🎉 Milestone: {$milestoneCount} people loved your blog post!"; + $headline = "🎉 Exciting News! {$milestoneCount} Love Reactions Milestone reached!"; + $message = "

Your article \"{$blogTitle}\" just hit {$milestoneCount} love reactions, with the latest from {$reactorName}!

" + .'

Thank you for creating content that resonates with our students and readers.

'; + } + + $mailContent = "

{$headline}

" + .$message + .'

' + ."" + .'View Blog Post →' + .'' + .'

'; + + return new self($mailSubject, $mailContent, $blog->user?->name); + } + + public function envelope(): Envelope + { + return new Envelope( + subject: $this->mailSubject, + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.bulk_announcement', + with: [ + 'mailSubject' => $this->mailSubject, + 'mailContent' => $this->mailContent, + 'recipientName' => $this->recipientName, + 'imageUrl' => null, + ], + ); + } +} diff --git a/resources/js/pages/Blog/Show.vue b/resources/js/pages/Blog/Show.vue index 00348aa..10513fb 100644 --- a/resources/js/pages/Blog/Show.vue +++ b/resources/js/pages/Blog/Show.vue @@ -445,26 +445,29 @@ const goBack = () => { - -
+
-
-

- Want to write a blog on this site? -

-

- Share your thoughts, stories, and expertise with our - community. -

+
+ Want to write a blog here? + +
- Join us + Join us + -
+
{ " @click="deleteComment(comment.id)" title="Delete comment" - class="cursor-pointer rounded-lg p-1.5 text-slate-400 opacity-0 transition-opacity group-hover:opacity-100 hover:bg-rose-50 hover:text-rose-600 dark:text-gray-500 dark:hover:bg-rose-950/40 dark:hover:text-rose-400" + class="cursor-pointer rounded-lg p-1.5 text-slate-400 opacity-100 transition-opacity hover:bg-rose-50 hover:text-rose-600 sm:opacity-0 sm:group-hover:opacity-100 dark:text-gray-500 dark:hover:bg-rose-950/40 dark:hover:text-rose-400" > diff --git a/tests/Feature/BlogTest.php b/tests/Feature/BlogTest.php index 6dd715d..95f68c7 100644 --- a/tests/Feature/BlogTest.php +++ b/tests/Feature/BlogTest.php @@ -1,6 +1,6 @@ assertSessionHas('success'); - Mail::assertQueued(BlogCommentNotificationMail::class, function ($mail) use ($author) { + Mail::assertQueued(BlogNotificationMail::class, function ($mail) use ($author) { return $mail->hasTo($author->email); }); }); @@ -181,3 +181,38 @@ Mail::assertNothingQueued(); }); + +test('email is queued to author on milestone reactions', function () { + Mail::fake(); + + $author = User::factory()->create(['email' => 'author@example.com', 'receive_emails' => true]); + $blog = Blog::factory()->create([ + 'user_id' => $author->id, + 'title' => 'Milestone Post', + 'is_published' => true, + ]); + + $firstUser = User::factory()->create(['name' => 'First Lover']); + $secondUser = User::factory()->create(['name' => 'Second Lover']); + + // 1st reaction (milestone: 1) -> queues email + $this->actingAs($firstUser)->post("/blogs/{$blog->slug}/react"); + Mail::assertQueued(BlogNotificationMail::class, 1); + + // 2nd reaction (not milestone) -> does not queue new email + $this->actingAs($secondUser)->post("/blogs/{$blog->slug}/react"); + Mail::assertQueued(BlogNotificationMail::class, 1); +}); + +test('email is not sent if author reacts to own blog', function () { + Mail::fake(); + + $author = User::factory()->create(['email' => 'author@example.com', 'receive_emails' => true]); + $blog = Blog::factory()->create([ + 'user_id' => $author->id, + 'is_published' => true, + ]); + + $this->actingAs($author)->post("/blogs/{$blog->slug}/react"); + Mail::assertNothingQueued(); +});