From fcecbe1c68b0d28d19454fba65caf3ab769fbc75 Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Mon, 16 Aug 2021 15:45:36 +0000
Subject: [PATCH] fix(comments): add comment view partials for public pages

---
 app/Config/Routes.php                         |  8 ++++
 app/Controllers/Admin/EpisodeController.php   | 41 +++++++++++++++++++
 app/Controllers/EpisodeCommentController.php  |  8 ++++
 app/Entities/EpisodeComment.php               | 14 +++++++
 app/Language/en/Comment.php                   | 10 +++++
 app/Models/EpisodeCommentModel.php            | 17 ++++++--
 app/Views/podcast/_partials/comment.php       |  2 +-
 .../podcast/_partials/comment_actions.php     | 21 +++-------
 .../comment_actions_authenticated.php         | 24 +++++++++++
 .../_partials/comment_actions_from_post.php   | 21 +++-------
 ...omment_actions_from_post_authenticated.php | 24 +++++++++++
 .../_partials/comment_authenticated.php       | 33 ++++++++-------
 app/Views/podcast/_partials/comment_card.php  | 38 +++++++++++++++++
 .../_partials/comment_card_authenticated.php  | 40 ++++++++++++++++++
 app/Views/podcast/_partials/comment_reply.php | 19 +++++++++
 .../_partials/comment_reply_actions.php       | 20 +++++++++
 .../comment_reply_actions_authenticated.php   | 17 ++++++++
 .../_partials/comment_reply_authenticated.php | 19 +++++++++
 .../_partials/comment_with_replies.php        | 20 ++-------
 .../comment_with_replies_authenticated.php    |  8 ++--
 .../podcast/_partials/post_authenticated.php  |  2 +-
 app/Views/podcast/comment.php                 |  2 +-
 app/Views/podcast/comment_authenticated.php   |  8 ++--
 app/Views/podcast/episode_authenticated.php   |  2 +-
 24 files changed, 338 insertions(+), 80 deletions(-)
 create mode 100644 app/Views/podcast/_partials/comment_actions_authenticated.php
 create mode 100644 app/Views/podcast/_partials/comment_actions_from_post_authenticated.php
 create mode 100644 app/Views/podcast/_partials/comment_card.php
 create mode 100644 app/Views/podcast/_partials/comment_card_authenticated.php
 create mode 100644 app/Views/podcast/_partials/comment_reply.php
 create mode 100644 app/Views/podcast/_partials/comment_reply_actions.php
 create mode 100644 app/Views/podcast/_partials/comment_reply_actions_authenticated.php
 create mode 100644 app/Views/podcast/_partials/comment_reply_authenticated.php

diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index e59d33895f..27c5ab80ab 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -426,6 +426,14 @@ $routes->group(
                                     'filter' => 'permission:podcast-manage_publications',
                                 ]
                             );
+                            $routes->post(
+                                '(:uuid)/reply',
+                                'EpisodeController::attemptCommentReply/$1/$2/$3',
+                                [
+                                    'as' => 'comment-attempt-reply',
+                                    'filter' => 'permission:podcast-manage_publications',
+                                ]
+                            );
                             $routes->post(
                                 'delete',
                                 'EpisodeController::attemptCommentDelete/$1/$2',
diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php
index 665c8007c5..fce864bd1b 100644
--- a/app/Controllers/Admin/EpisodeController.php
+++ b/app/Controllers/Admin/EpisodeController.php
@@ -821,4 +821,45 @@ class EpisodeController extends BaseController
         // Comment has been successfully created
         return redirect()->back();
     }
+
+    public function attemptCommentReply(string $commentId): RedirectResponse
+    {
+        // var_dump($commentId);
+        // die();
+
+        $rules = [
+            'message' => 'required|max_length[500]',
+        ];
+
+        if (! $this->validate($rules)) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $this->validator->getErrors());
+        }
+
+        $message = $this->request->getPost('message');
+
+        $newReply = new EpisodeComment([
+            'actor_id' => interact_as_actor_id(),
+            'episode_id' => $this->episode->id,
+            'message' => $message,
+            'in_reply_to_id' => $commentId,
+            'created_at' => new Time('now'),
+            'created_by' => user_id(),
+        ]);
+
+        $commentModel = new EpisodeCommentModel();
+        if (
+            ! $commentModel->addComment($newReply, true)
+        ) {
+            return redirect()
+                ->back()
+                ->withInput()
+                ->with('errors', $commentModel->errors());
+        }
+
+        // Reply has been successfully created
+        return redirect()->back();
+    }
 }
diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php
index 4f503d39e1..12ccf9d605 100644
--- a/app/Controllers/EpisodeCommentController.php
+++ b/app/Controllers/EpisodeCommentController.php
@@ -170,4 +170,12 @@ class EpisodeCommentController extends BaseController
 
         return redirect()->back();
     }
+
+    public function attemptReply(): RedirectResponse
+    {
+        model('LikeModel')
+            ->toggleLike(interact_as_actor(), $this->comment);
+
+        return redirect()->back();
+    }
 }
diff --git a/app/Entities/EpisodeComment.php b/app/Entities/EpisodeComment.php
index db2622c1f7..a6fa142009 100644
--- a/app/Entities/EpisodeComment.php
+++ b/app/Entities/EpisodeComment.php
@@ -118,6 +118,20 @@ class EpisodeComment extends UuidEntity
         return $this->replies;
     }
 
+    public function getReplyToComment(): ?self
+    {
+        if ($this->in_reply_to_id === null) {
+            throw new RuntimeException('Comment is not a reply.');
+        }
+
+        if ($this->reply_to_comment === null) {
+            $this->reply_to_comment = model('EpisodeCommentModel', false)
+                ->getCommentById($this->in_reply_to_id);
+        }
+
+        return $this->reply_to_comment;
+    }
+
     public function setMessage(string $message): static
     {
         helper('activitypub');
diff --git a/app/Language/en/Comment.php b/app/Language/en/Comment.php
index d3677dc7d2..aff97d5ee6 100644
--- a/app/Language/en/Comment.php
+++ b/app/Language/en/Comment.php
@@ -9,12 +9,22 @@ declare(strict_types=1);
  */
 
 return [
+    'title' => "{actorDisplayName}'s comment for {episodeTitle}",
+    'back_to_episode' => 'Back to {episodeTitle}',
     'form' => [
         'episode_message_placeholder' => 'Write a comment...',
         'reply_to_placeholder' => 'Reply to @{actorUsername}',
         'submit' => 'Send!',
         'submit_reply' => 'Reply',
     ],
+    'likes' => '{numberOfLikes, plural,
+        one {# like}
+        other {# likes}
+    }',
+    'replies' => '{numberOfReplies, plural,
+        one {# reply}
+        other {# replies}
+    }',
     'like' => 'Like',
     'reply' => 'Reply',
     'view_replies' => 'View replies ({numberOfReplies})',
diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php
index 794dab0e6d..a5e4abf020 100644
--- a/app/Models/EpisodeCommentModel.php
+++ b/app/Models/EpisodeCommentModel.php
@@ -80,9 +80,15 @@ class EpisodeCommentModel extends UuidModel
             return false;
         }
 
-        (new EpisodeModel())
-            ->where('id', $comment->episode_id)
-            ->increment('comments_count');
+        if ($comment->in_reply_to_id === null) {
+            (new EpisodeModel())
+                ->where('id', $comment->episode_id)
+                ->increment('comments_count');
+        } else {
+            (new self())
+                ->where('id', service('uuid')->fromString($comment->in_reply_to_id)->getBytes())
+                ->increment('replies_count');
+        }
 
         if ($registerActivity) {
             // set post id and uri to construct NoteObject
@@ -129,7 +135,10 @@ class EpisodeCommentModel extends UuidModel
     {
         // TODO: merge with replies from posts linked to episode linked
         $episodeComments = $this->select('*, 0 as is_from_post')
-            ->where('episode_id', $episodeId)
+            ->where([
+                'episode_id' => $episodeId,
+                'in_reply_to_id' => null,
+            ])
             ->getCompiledSelect();
 
         $episodePostsReplies = $this->db->table('activitypub_posts')
diff --git a/app/Views/podcast/_partials/comment.php b/app/Views/podcast/_partials/comment.php
index 89ae770071..252914c40a 100644
--- a/app/Views/podcast/_partials/comment.php
+++ b/app/Views/podcast/_partials/comment.php
@@ -20,7 +20,7 @@
         <?php if ($comment->is_from_post): ?>
             <?= $this->include('podcast/_partials/comment_actions_from_post') ?>
         <?php else: ?>
-                <?= $this->include('podcast/_partials/comment_actions') ?>
+            <?= $this->include('podcast/_partials/comment_actions') ?>
         <?php endif; ?>
     </div>
 </article>
diff --git a/app/Views/podcast/_partials/comment_actions.php b/app/Views/podcast/_partials/comment_actions.php
index cb92fda5b9..3be7662517 100644
--- a/app/Views/podcast/_partials/comment_actions.php
+++ b/app/Views/podcast/_partials/comment_actions.php
@@ -1,19 +1,10 @@
 <footer>
-    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
-        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
-            'Comment.like',
-            [
-                'numberOfLikes' => $comment->likes_count,
-            ],
-        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
-        <?= button(
-            lang('Comment.reply'),
-            route_to('comment', $podcast->handle, $episode->slug, $comment->id),
-            [
-                'size' => 'small',
-            ],
-        ) ?>
-    </form>
+    <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
+        'Comment.like',
+        [
+            'numberOfLikes' => $comment->likes_count,
+        ],
+    ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
     <?php if($comment->replies_count): ?>
         <?= anchor(
             route_to('comment', $podcast->handle, $episode->slug, $comment->id),
diff --git a/app/Views/podcast/_partials/comment_actions_authenticated.php b/app/Views/podcast/_partials/comment_actions_authenticated.php
new file mode 100644
index 0000000000..ad55f02327
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_actions_authenticated.php
@@ -0,0 +1,24 @@
+<footer>
+    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+        <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
+            'Comment.likes',
+            [
+                'numberOfLikes' => $comment->likes_count,
+            ],
+        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
+        <?= button(
+            lang('Comment.reply'),
+            route_to('comment', $podcast->handle, $episode->slug, $comment->id),
+            [
+                'size' => 'small',
+            ],
+        ) ?>
+    </form>
+    <?php if($comment->replies_count): ?>
+        <?= anchor(
+            route_to('comment', $podcast->handle, $episode->slug, $comment->id),
+            icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', ['numberOfReplies' => $comment->replies_count]),
+            ['class' => 'inline-flex items-center text-xs hover:underline']
+        ) ?>
+    <?php endif; ?>
+</footer>
\ No newline at end of file
diff --git a/app/Views/podcast/_partials/comment_actions_from_post.php b/app/Views/podcast/_partials/comment_actions_from_post.php
index de10aa3b6f..c7a23b0d2d 100644
--- a/app/Views/podcast/_partials/comment_actions_from_post.php
+++ b/app/Views/podcast/_partials/comment_actions_from_post.php
@@ -1,19 +1,10 @@
 <footer>
-    <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
-        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
-            'Comment.like',
-            [
-                'numberOfLikes' => $comment->likes_count,
-            ],
-        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
-        <?= button(
-            lang('Comment.reply'),
-            route_to('post', $podcast->handle, $comment->id),
-            [
-                'size' => 'small',
-            ],
-        ) ?>
-    </form>
+    <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
+        'Comment.like',
+        [
+            'numberOfLikes' => $comment->likes_count,
+        ],
+    ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
     <?php if($comment->replies_count): ?>
         <?= anchor(
             route_to('post', $podcast->handle, $comment->id),
diff --git a/app/Views/podcast/_partials/comment_actions_from_post_authenticated.php b/app/Views/podcast/_partials/comment_actions_from_post_authenticated.php
new file mode 100644
index 0000000000..5d06650c38
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_actions_from_post_authenticated.php
@@ -0,0 +1,24 @@
+<footer>
+    <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
+            'Comment.likes',
+            [
+                'numberOfLikes' => $comment->likes_count,
+            ],
+        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
+        <?= button(
+            lang('Comment.reply'),
+            route_to('post', $podcast->handle, $comment->id),
+            [
+                'size' => 'small',
+            ],
+        ) ?>
+    </form>
+    <?php if($comment->replies_count): ?>
+        <?= anchor(
+            route_to('post', $podcast->handle, $comment->id),
+            icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', ['numberOfReplies' => $comment->replies_count]),
+            ['class' => 'inline-flex items-center text-xs hover:underline']
+        ) ?>
+    <?php endif; ?>
+</footer>
\ No newline at end of file
diff --git a/app/Views/podcast/_partials/comment_authenticated.php b/app/Views/podcast/_partials/comment_authenticated.php
index 75055677f3..74bedf6d7e 100644
--- a/app/Views/podcast/_partials/comment_authenticated.php
+++ b/app/Views/podcast/_partials/comment_authenticated.php
@@ -1,13 +1,11 @@
-<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
-    <header class="flex px-6 py-4">
-        <img src="<?= $comment->actor
-            ->avatar_image_url ?>" alt="<?= $comment->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-        <div class="flex flex-col min-w-0">
+<article class="relative z-10 flex w-full px-4 py-2 rounded-2xl">
+    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+    <div class="flex-1">
+        <header class="w-full mb-2 text-sm">
             <a href="<?= $comment->actor
-                ->uri ?>" class="flex items-baseline hover:underline" <?= $comment
-    ->actor->is_local
-    ? ''
-    : 'target="_blank" rel="noopener noreferrer"' ?>>
+                ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
+                ? ''
+                : 'target="_blank" rel="noopener noreferrer"' ?>>
                 <span class="mr-2 font-semibold truncate"><?= $comment->actor
                     ->display_name ?></span>
                 <span class="text-sm text-gray-500 truncate">@<?= $comment->actor
@@ -15,13 +13,14 @@
                     ($comment->actor->is_local
                         ? ''
                         : '@' . $comment->actor->domain) ?></span>
+                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
             </a>
-            <a href="<?= route_to('comment', $podcast->handle, $episode->slug, $comment->id) ?>"
-            class="text-xs text-gray-500">
-                <?= relative_time($comment->created_at) ?>
-            </a>
-        </div>
-    </header>
-    <div class="px-6 mb-4 post-content"><?= $comment->message_html ?></div>
-    <?= $this->include('podcast/_partials/comment_actions') ?>
+        </header>
+        <div class="mb-2 post-content"><?= $comment->message_html ?></div>
+        <?php if ($comment->is_from_post): ?>
+            <?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
+        <?php else: ?>
+            <?= $this->include('podcast/_partials/comment_actions_authenticated') ?>
+        <?php endif; ?>
+    </div>
 </article>
diff --git a/app/Views/podcast/_partials/comment_card.php b/app/Views/podcast/_partials/comment_card.php
new file mode 100644
index 0000000000..e2910901a4
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_card.php
@@ -0,0 +1,38 @@
+<article class="relative z-10 flex w-full p-4 bg-white shadow rounded-2xl">
+    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+    <div class="flex-1">
+        <header class="w-full mb-2 text-sm">
+            <a href="<?= $comment->actor
+                ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
+                ? ''
+                : 'target="_blank" rel="noopener noreferrer"' ?>>
+                <span class="mr-2 font-semibold truncate"><?= $comment->actor
+                    ->display_name ?></span>
+                <span class="text-sm text-gray-500 truncate">@<?= $comment->actor
+                    ->username .
+                    ($comment->actor->is_local
+                        ? ''
+                        : '@' . $comment->actor->domain) ?></span>
+                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
+            </a>
+        </header>
+        <div class="mb-2 post-content"><?= $comment->message_html ?></div>
+        <?php if ($comment->is_from_post): ?>
+            <?= $this->include('podcast/_partials/comment_actions_from_post') ?>
+        <?php else: ?>
+            <footer>
+                <button class="inline-flex items-center opacity-50 cursor-not-allowed" title="<?= lang(
+                    'Comment.likes',
+                    [
+                        'numberOfLikes' => $comment->likes_count,
+                    ],
+                ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . lang(
+                    'Comment.likes',
+                    [
+                        'numberOfLikes' => $comment->likes_count,
+                    ],
+                ) ?></button>
+            </footer>
+        <?php endif; ?>
+    </div>
+</article>
diff --git a/app/Views/podcast/_partials/comment_card_authenticated.php b/app/Views/podcast/_partials/comment_card_authenticated.php
new file mode 100644
index 0000000000..66f3cfc133
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_card_authenticated.php
@@ -0,0 +1,40 @@
+<article class="relative z-10 flex w-full p-4 bg-white shadow rounded-2xl">
+    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+    <div class="flex-1">
+        <header class="w-full mb-2 text-sm">
+            <a href="<?= $comment->actor
+                ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
+                ? ''
+                : 'target="_blank" rel="noopener noreferrer"' ?>>
+                <span class="mr-2 font-semibold truncate"><?= $comment->actor
+                    ->display_name ?></span>
+                <span class="text-sm text-gray-500 truncate">@<?= $comment->actor
+                    ->username .
+                    ($comment->actor->is_local
+                        ? ''
+                        : '@' . $comment->actor->domain) ?></span>
+                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
+            </a>
+        </header>
+        <div class="mb-2 post-content"><?= $comment->message_html ?></div>
+        <?php if ($comment->is_from_post): ?>
+            <?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
+        <?php else: ?>
+            <footer>
+                <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+                    <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
+                        'Comment.likes',
+                        [
+                            'numberOfLikes' => $comment->likes_count,
+                        ],
+                    ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . lang(
+                        'Comment.likes',
+                        [
+                            'numberOfLikes' => $comment->likes_count,
+                        ],
+                    ) ?></button>
+                </form>
+            </footer>
+        <?php endif; ?>
+    </div>
+</article>
diff --git a/app/Views/podcast/_partials/comment_reply.php b/app/Views/podcast/_partials/comment_reply.php
new file mode 100644
index 0000000000..823b07f19e
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_reply.php
@@ -0,0 +1,19 @@
+<article class="flex px-6 py-4 bg-gray-50">
+    <img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
+    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
+    <div class="flex flex-col flex-1 min-w-0">
+        <header class="flex items-center mb-2">
+            <a href="<?= $reply->actor
+                ->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
+    ->actor->is_local
+    ? ''
+    : 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
+    ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
+    ->actor->username .
+    ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
+            <?= relative_time($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
+        </header>
+        <p class="mb-2 post-content"><?= $reply->message_html ?></p>
+        <?= $this->include('podcast/_partials/comment_reply_actions') ?>
+    </div>
+</article>
diff --git a/app/Views/podcast/_partials/comment_reply_actions.php b/app/Views/podcast/_partials/comment_reply_actions.php
new file mode 100644
index 0000000000..aa8a37b3ff
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_reply_actions.php
@@ -0,0 +1,20 @@
+<footer class="flex items-center gap-x-4">
+    <button type="submit" name="action" class="inline-flex items-center opacity-50 cursor-not-allowed" disabled="disabled" title="<?= lang(
+            'Comment.likes',
+            [
+                'numberOfLikes' => $reply->likes_count,
+            ],
+    ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $reply->likes_count ?></button>
+    <?php if($reply->replies_count): ?>
+    <?= anchor(
+        route_to('comment', $podcast->handle, $episode->slug, $reply->id),
+        icon('chat', 'text-2xl mr-1 text-gray-400') . $reply->replies_count,
+        [
+            'class' => 'inline-flex items-center hover:underline',
+            'title' => lang('Comment.replies', [
+                'numberOfReplies' => $reply->replies_count,
+            ]),
+        ],
+    ) ?>
+    <?php endif; ?>
+</footer>
\ No newline at end of file
diff --git a/app/Views/podcast/_partials/comment_reply_actions_authenticated.php b/app/Views/podcast/_partials/comment_reply_actions_authenticated.php
new file mode 100644
index 0000000000..a007c2b763
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_reply_actions_authenticated.php
@@ -0,0 +1,17 @@
+<footer>
+    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $reply->id) ?>" method="POST" class="flex items-center gap-x-4">
+        <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
+            'Comment.likes',
+            [
+                'numberOfLikes' => $reply->likes_count,
+            ],
+        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $reply->likes_count ?></button>
+        <?= button(
+            lang('Comment.reply'),
+            route_to('comment', $podcast->handle, $episode->slug, $reply->id),
+            [
+                'size' => 'small',
+            ],
+        ) ?>
+    </form>
+</footer>
\ No newline at end of file
diff --git a/app/Views/podcast/_partials/comment_reply_authenticated.php b/app/Views/podcast/_partials/comment_reply_authenticated.php
new file mode 100644
index 0000000000..2f51c66ac9
--- /dev/null
+++ b/app/Views/podcast/_partials/comment_reply_authenticated.php
@@ -0,0 +1,19 @@
+<article class="flex px-6 py-4 bg-gray-50">
+    <img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
+    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
+    <div class="flex flex-col flex-1 min-w-0">
+        <header class="flex items-center mb-2">
+            <a href="<?= $reply->actor
+                ->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
+    ->actor->is_local
+    ? ''
+    : 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
+    ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
+    ->actor->username .
+    ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
+            <?= relative_time($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
+        </header>
+        <p class="mb-2 post-content"><?= $reply->message_html ?></p>
+        <?= $this->include('podcast/_partials/comment_reply_actions_authenticated') ?>
+    </div>
+</article>
diff --git a/app/Views/podcast/_partials/comment_with_replies.php b/app/Views/podcast/_partials/comment_with_replies.php
index fcd817d771..b569c1a947 100644
--- a/app/Views/podcast/_partials/comment_with_replies.php
+++ b/app/Views/podcast/_partials/comment_with_replies.php
@@ -1,22 +1,8 @@
-<?= $this->include('podcast/_partials/comment') ?>
-<div class="-mt-2 overflow-hidden border-b border-l border-r comment-replies rounded-b-xl">
-
-<div class="px-6 pt-8 pb-4 bg-gray-50">
-<?= anchor_popup(
-    route_to('comment-remote-action', $podcast->handle, $comment->id, 'reply'),
-    lang('comment.reply_to', ['actorUsername' => $comment->actor->username]),
-    [
-        'class' =>
-            'text-center justify-center font-semibold rounded-full shadow relative z-10 px-4 py-2 w-full bg-rose-600 text-white inline-flex items-center hover:bg-rose-700',
-        'width' => 420,
-        'height' => 620,
-    ],
-) ?>
-</div>
-
+<?= $this->include('podcast/_partials/comment_card') ?>
+<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
 
 <?php foreach ($comment->replies as $reply): ?>
-    <?= view('podcast/_partials/comment', ['comment' => $reply]) ?>
+    <?= view('podcast/_partials/comment_reply', ['reply' => $reply]) ?>
 <?php endforeach; ?>
 
 </div>
diff --git a/app/Views/podcast/_partials/comment_with_replies_authenticated.php b/app/Views/podcast/_partials/comment_with_replies_authenticated.php
index c74c07d4ef..567137b395 100644
--- a/app/Views/podcast/_partials/comment_with_replies_authenticated.php
+++ b/app/Views/podcast/_partials/comment_with_replies_authenticated.php
@@ -1,7 +1,7 @@
-<?= $this->include('podcast/_partials/comment_authenticated') ?>
+<?= $this->include('podcast/_partials/comment_card_authenticated') ?>
 <div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
 <?= form_open(
-    route_to('comment-attempt-action', interact_as_actor()->username, $episode->slug, $comment->id),
+    route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id),
     [
         'class' => 'bg-gray-50 flex px-6 pt-8 pb-4',
     ],
@@ -40,8 +40,8 @@
 <?= form_close() ?>
 
 <?php foreach ($comment->replies as $reply): ?>
-    <?= view('podcast/_partials/comment_authenticated', [
-        'comment' => $reply,
+    <?= view('podcast/_partials/comment_reply_authenticated', [
+        'reply' => $reply,
     ]) ?>
 <?php endforeach; ?>
 </div>
diff --git a/app/Views/podcast/_partials/post_authenticated.php b/app/Views/podcast/_partials/post_authenticated.php
index bb4ac6b4a0..da2b505531 100644
--- a/app/Views/podcast/_partials/post_authenticated.php
+++ b/app/Views/podcast/_partials/post_authenticated.php
@@ -1,4 +1,4 @@
-<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
+<article class="relative z-10 w-full bg-white shadow rounded-2xl">
     <header class="flex px-6 py-4">
         <img src="<?= $post->actor
             ->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
diff --git a/app/Views/podcast/comment.php b/app/Views/podcast/comment.php
index 9e604078cd..ee9c0bcaf8 100644
--- a/app/Views/podcast/comment.php
+++ b/app/Views/podcast/comment.php
@@ -26,7 +26,7 @@
             'mr-2 text-lg',
         ) .
             lang('Comment.back_to_episode', [
-                'actor' => $comment->actor->display_name,
+                'episodeTitle' => $episode->title,
             ]) ?></a>
     </nav>
     <div class="pb-12">
diff --git a/app/Views/podcast/comment_authenticated.php b/app/Views/podcast/comment_authenticated.php
index 89965611c2..edf7307fa8 100644
--- a/app/Views/podcast/comment_authenticated.php
+++ b/app/Views/podcast/comment_authenticated.php
@@ -3,11 +3,12 @@
 <?= $this->section('meta-tags') ?>
     <title><?= lang('Comment.title', [
         'actorDisplayName' => $comment->actor->display_name,
+        'episodeTitle' => $episode->title
     ]) ?></title>
     <meta name="description" content="<?= $comment->message ?>"/>
     <meta property="og:title" content="<?= lang('Comment.title', [
         'actorDisplayName' => $comment->actor->display_name,
-    ]) ?>"/>
+        'episodeTitle' => $episode->title    ]) ?>"/>
     <meta property="og:locale" content="<?= service(
         'request',
     )->getLocale() ?>" />
@@ -26,7 +27,7 @@
             'mr-2 text-lg',
         ) .
             lang('Comment.back_to_episode', [
-                'actor' => $comment->actor->display_name,
+                'episodeTitle' => $episode->title,
             ]) ?></a>
     </nav>
     <div class="pb-12">
@@ -36,5 +37,4 @@
     </div>
 </div>
 
-<?= $this->endSection()
-?>
+<?= $this->endSection() ?>
diff --git a/app/Views/podcast/episode_authenticated.php b/app/Views/podcast/episode_authenticated.php
index 59e9b69b24..b98765257c 100644
--- a/app/Views/podcast/episode_authenticated.php
+++ b/app/Views/podcast/episode_authenticated.php
@@ -119,7 +119,7 @@
                 </div>
                 <?= form_close() ?>
                 <?php foreach ($episode->comments as $comment): ?>
-                    <?= view('podcast/_partials/comment', ['comment' => $comment]) ?>
+                    <?= view('podcast/_partials/comment_authenticated', ['comment' => $comment]) ?>
                 <?php endforeach; ?>
             </section>
             <section id="activity" class="space-y-8 tab-panel">
-- 
GitLab