Repository: jin-hatanaka/twitter_clone
Pull request #4: プロフィールページ作成
Author: login: jin-hatanaka
Created: 2025-05-12T21:20:09Z | Merged: 2025-05-15T05:09:47Z

プロフィールページの実装をしました。
ご確認お願いいたします！

・herokuのURL
https://protected-peak-36056-bd27eb82b192.herokuapp.com/


Diff:
diff --git a/app/assets/images/header1.png b/app/assets/images/header1.png
new file mode 100644
index 0000000..f94d4f8
Binary files /dev/null and b/app/assets/images/header1.png differ
diff --git a/app/assets/stylesheets/application.bootstrap.scss b/app/assets/stylesheets/application.bootstrap.scss
index b93808b..88088cd 100644
--- a/app/assets/stylesheets/application.bootstrap.scss
+++ b/app/assets/stylesheets/application.bootstrap.scss
@@ -198,7 +198,7 @@ ul {
 
 #recommend-tab-pane,
 #follow-tab-pane {
-  height: 682px;
+  height: 679px;
   overflow-y: scroll;
 }
 
@@ -206,6 +206,11 @@ ul {
 .post-display-section {
   .post-user-img {
     margin-right: 8px;
+    transition: background-color 0.2s;
+
+    &:hover {
+      background-color: rgba(0 0 0 / 0.1);
+    }
   }
 }
 
@@ -281,5 +286,141 @@ ul {
     gap: 87px;
     color: rgb(83, 100, 113);
     padding: 8px 0 12px;
+    font-size: 15px;
+
+    .bi-arrow-repeat.active,
+    .retweeted {
+      color: rgb(0, 170, 0);
+    }
+
+    .bi-heart-fill,
+    .liked {
+      color: red;
+    }
+
+    span {
+      font-size: 13px;
+      padding: 0 4px;
+    }
+  }
+}
+
+/* プロフィールページの設定 */
+.profile-bar {
+  padding: 0 12px;
+
+  .back-btn {
+    border: none;
+    background-color: inherit;
+    width: 35px;
+    height: 35px;
+    border-radius: 50%;
+    font-size: 20px;
+    transition: background-color 0.2s;
+
+    &:hover {
+      background-color: rgba(0 0 0 / 0.1);
+    }
+  }
+
+  .profile-bar-username {
+    font-weight: bold;
+    font-size: 20px;
+  }
+
+  .posts-number {
+    font-size: 13px;
+    color: #536471;
+  }
+}
+
+.profile-contents {
+  position: relative;
+  overflow-y: scroll;
+  height: 674px;
+
+  .no-header-img {
+    background-color: rgb(218, 218, 218);
+    height: 200px;
+  }
+
+  .profile-user-img-wrapper {
+    width: 143px;
+    height: 143px;
+    background-color: white;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: absolute;
+    top: 125px;
+    left: 16px;
+
+    .profile-user-img {
+      border-radius: 50%;
+    }
+  }
+
+  .profile-info {
+    padding: 12px 16px 0;
+    margin: 0 0 16px;
+
+    .profile-edit-btn {
+      border: solid 1px rgb(0 0 0 / 0.15);
+      background-color: inherit;
+      font-size: 15px;
+      font-weight: bold;
+      width: 168px;
+      height: 36px;
+      border-radius: 50px;
+      margin: 0 0 34px;
+      transition: background-color 0.2s;
+
+      &:hover {
+        background-color: rgba(0 0 0 / 0.1);
+      }
+    }
+
+    .profile-username {
+      font-size: 20px;
+      font-weight: bold;
+      margin: 0 0 20px;
+    }
+
+    .self-introduction {
+      font-size: 15px;
+      margin: 0 0 10px;
+    }
+
+    .profile-others {
+      width: 490px;
+      margin: 0 0 12px;
+
+      span {
+        font-size: 15px;
+        color: #536471;
+        margin-right: 10px;
+        display: inline-block;
+
+        i {
+          font-size: 16px;
+          margin-right: 4px;
+        }
+      }
+    }
+
+    .follow-number-wrapper {
+      font-size: 14px;
+      margin-right: 20px;
+
+      .follow-number {
+        font-weight: bold;
+        margin-right: 3px;
+      }
+
+      .follow-text {
+        color: #536471;
+      }
+    }
   }
 }
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
new file mode 100644
index 0000000..7ad638f
--- /dev/null
+++ b/app/controllers/profiles_controller.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class ProfilesController < ApplicationController
+  before_action :set_tweet_tab, only: [:index]
+
+  def index
+    @tweets = current_user.tweets.order(created_at: 'DESC').page(params[:tweet]).per(2)
+    @like_tweets = current_user.like_tweets.order(created_at: 'DESC').page(params[:like]).per(2)
+    @retweet_tweets = current_user.retweet_tweets.order(created_at: 'DESC').page(params[:retweet]).per(2)
+    @comment_tweets = current_user.comment_tweets.order(created_at: 'DESC').page(params[:comment]).per(2)
+  end
+
+  def edit; end
+
+  private
+
+  # デフォルトでツイートタブが見えるようにする
+  def set_tweet_tab
+    redirect_to profiles_path(tab: 'tweet') if params[:tab].nil?
+  end
+end
diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb
index 2e1b19c..4d51287 100644
--- a/app/controllers/tweets_controller.rb
+++ b/app/controllers/tweets_controller.rb
@@ -4,9 +4,11 @@ class TweetsController < ApplicationController
   before_action :authenticate_user!
 
   def index
+    # デフォルトでおすすめタブが見えるようにする
+    redirect_to root_path(tab: 'recommend') if params[:tab].nil?
     @tweets = Tweet.all.order(created_at: 'DESC').page(params[:recommend]).per(5)
                    .with_attached_images.includes(user: { icon_image_attachment: :blob })
-    @following_users_tweets = @tweets.where(user_id: current_user.following_user_id).page(params[:follow]).per(5)
+    @following_user_tweets = @tweets.where(user_id: current_user.following_user_ids).page(params[:follow]).per(5)
   end
 
   def show; end
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
new file mode 100644
index 0000000..9ba602e
--- /dev/null
+++ b/app/helpers/profiles_helper.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+module ProfilesHelper
+end
diff --git a/app/models/comment.rb b/app/models/comment.rb
new file mode 100644
index 0000000..f7220c9
--- /dev/null
+++ b/app/models/comment.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class Comment < ApplicationRecord
+  belongs_to :user
+  belongs_to :tweet
+  has_many_attached :images
+end
diff --git a/app/models/like.rb b/app/models/like.rb
new file mode 100644
index 0000000..750bc39
--- /dev/null
+++ b/app/models/like.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class Like < ApplicationRecord
+  belongs_to :user
+  belongs_to :tweet
+
+  validates :user_id, uniqueness: { scope: :tweet_id }
+end
diff --git a/app/models/retweet.rb b/app/models/retweet.rb
new file mode 100644
index 0000000..30d33dd
--- /dev/null
+++ b/app/models/retweet.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class Retweet < ApplicationRecord
+  belongs_to :user
+  belongs_to :tweet
+
+  validates :user_id, uniqueness: { scope: :tweet_id }
+end
diff --git a/app/models/tweet.rb b/app/models/tweet.rb
index 77bf53d..68ff857 100644
--- a/app/models/tweet.rb
+++ b/app/models/tweet.rb
@@ -2,5 +2,17 @@
 
 class Tweet < ApplicationRecord
   belongs_to :user
+  has_many :likes, dependent: :destroy
+  has_many :retweets, dependent: :destroy
+  has_many :comments, dependent: :destroy
   has_many_attached :images
+
+  # tweetに結びついているいいね(like)の中で今いいねしようとしているuser_idが存在するか
+  def liked_by?(user)
+    likes.exists?(user_id: user.id)
+  end
+
+  def retweeted_by?(user)
+    retweets.exists?(user_id: user.id)
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 873a1c4..c6fcaf7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -8,7 +8,11 @@ class User < ApplicationRecord
          :confirmable, :lockable, :timeoutable, :trackable, :omniauthable, omniauth_providers: %i[github]
 
   has_many :tweets, dependent: :destroy
+  has_many :likes, dependent: :destroy
+  has_many :retweets, dependent: :destroy
+  has_many :comments, dependent: :destroy
   has_one_attached :icon_image
+  has_one_attached :header_image
 
   # フォローする側からのhas_many
   has_many :relationships, foreign_key: :following_id, dependent: :destroy, inverse_of: :following
@@ -21,6 +25,15 @@ class User < ApplicationRecord
   # 一覧画面で使用する（あるユーザーのフォロワー全員をとってくる->つまり、あるユーザーをフォローしている人をとってくる）
   has_many :followers, through: :reverse_of_relationships, source: :following
 
+  # ユーザーがいいねしたツイートをとってくる
+  has_many :like_tweets, through: :likes, source: :tweet
+
+  # ユーザーがリツイートしたツイートをとってくる
+  has_many :retweet_tweets, through: :retweets, source: :tweet
+
+  # ユーザーがコメントしたツイートをとってくる
+  has_many :comment_tweets, through: :comments, source: :tweet
+
   with_options presence: true do
     validates :email
     validates :phone_number
@@ -50,7 +63,8 @@ def self.create_unique_string
     SecureRandom.uuid
   end
 
-  def following_user_id
+  # フォローしているユーザーのid一覧
+  def following_user_ids
     followings.map(&:id)
   end
 end
diff --git a/app/views/layouts/_aside.html.slim b/app/views/layouts/_aside.html.slim
new file mode 100644
index 0000000..4d2760c
--- /dev/null
+++ b/app/views/layouts/_aside.html.slim
@@ -0,0 +1,8 @@
+aside.aside
+  div.search
+    input.form-control placeholder="検索" 
+  div.now-find
+    p 「いま」を見つけよう
+  div.recommend-user
+    p おすすめユーザー
+  div
\ No newline at end of file
diff --git a/app/views/layouts/_post.html.slim b/app/views/layouts/_post.html.slim
new file mode 100644
index 0000000..576b5fb
--- /dev/null
+++ b/app/views/layouts/_post.html.slim
@@ -0,0 +1,46 @@
+div.post-display-section
+  div.one-post-display.border-bottom
+    div.d-flex
+      div
+        = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
+      div.w-100
+        div.d-flex
+          span.post-username
+            = tweet.user.name
+          span.post-date.me-auto
+            | ・
+            = tweet.created_at.to_fs(:date_jp)
+          i.bi.bi-three-dots
+        div.post-content
+          span
+            = tweet.content
+        div.post-img-wrapper
+          - if tweet.images.attached?
+            - tweet.images.each do |image|
+              = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
+        div.d-flex.post-display-icon-list
+          div
+            i.bi.bi-chat
+            span
+              = tweet.comments.count
+          div
+            - if tweet.retweeted_by?(current_user)
+              i.bi.bi-arrow-repeat.active
+            - else
+              i.bi.bi-arrow-repeat
+            span class="#{'retweeted' if tweet.retweeted_by?(current_user)}"
+              = tweet.retweets.count
+          div.d-flex.align-items-center
+            - if tweet.liked_by?(current_user)
+              i.bi.bi-heart-fill
+            - else
+              i.bi.bi-heart
+            span class="#{'liked' if tweet.liked_by?(current_user)}"
+              = tweet.likes.count
+          div
+            i.bi.bi-bar-chart-fill
+          div.d-flex.ms-auto
+            div
+              i.bi.bi-bookmark.me-3
+            div
+              i.bi.bi-upload
\ No newline at end of file
diff --git a/app/views/layouts/_sidebar.html.slim b/app/views/layouts/_sidebar.html.slim
new file mode 100644
index 0000000..47bb0fe
--- /dev/null
+++ b/app/views/layouts/_sidebar.html.slim
@@ -0,0 +1,49 @@
+nav.nav
+  ul
+    li
+      = button_to "/", method: :get, class: "nav-btn" do
+        i.bi.bi-house-door
+        span.icon-name ホーム
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-search
+        span.icon-name 話題を検索
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-bell
+        span.icon-name 通知
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-envelope
+        span.icon-name メッセージ
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-bookmark
+        span.icon-name ブックマーク
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-people
+        span.icon-name コミュニティ
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-lightning
+        span.icon-name 認証済み組織
+    li
+      = button_to profiles_path, method: :get, class: "nav-btn" do
+        i.bi.bi-person
+        span.icon-name プロフィール
+    li
+      = button_to "/", class: "nav-btn" do
+        i.bi.bi-three-dots
+        span.icon-name もっと見る
+    li
+      button.btn.post-btn 
+        span ポストする
+    li
+      div.nav-user-info
+        = button_to "/", class: "nav-user-info-btn" do
+          = image_tag current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "user-img"
+          div.nav-user-info-name
+            = current_user.name
+          div.nav-user-info-dots
+            i.bi.bi-three-dots
\ No newline at end of file
diff --git a/app/views/profiles/edit.html.slim b/app/views/profiles/edit.html.slim
new file mode 100644
index 0000000..1fec6bb
--- /dev/null
+++ b/app/views/profiles/edit.html.slim
@@ -0,0 +1,2 @@
+h1 Profiles#edit
+p Find me in app/views/profiles/edit.html.slim
diff --git a/app/views/profiles/index.html.slim b/app/views/profiles/index.html.slim
new file mode 100644
index 0000000..38f861b
--- /dev/null
+++ b/app/views/profiles/index.html.slim
@@ -0,0 +1,92 @@
+div.toppage-container
+  = render 'layouts/sidebar'
+  main.main.border-start.border-end
+    div.profile-bar.d-flex
+      div.d-flex.align-items-center
+        = button_to "/", method: :get, class: "back-btn" do
+          i.bi.bi-arrow-left
+      div.ms-4
+        div.profile-bar-username
+          | ユーザー1
+        div.posts-number
+          = current_user.tweets.count
+          | 件のポスト
+    div.profile-contents
+      div.header-img-wrapper
+        - if current_user.header_image.attached?
+          = image_tag current_user.header_image.variant(gravity: :center, resize:"603x200^", crop:"603x200+0+0").processed, class: "header-img"
+        - else
+          div.no-header-img
+      div.profile-user-img-wrapper
+        = image_tag current_user.icon_image.variant(resize_to_fill: [134, 134]).processed, class: "profile-user-img"
+      div.profile-info
+        div.d-flex.justify-content-end
+          = button_to "プロフィールを編集", "/", class: "profile-edit-btn"
+        div.profile-username
+          = current_user.name
+        div.self-introduction
+          = simple_format(current_user.self_introduction)
+        div.profile-others
+          - if current_user.place?
+            span
+              i.bi.bi-geo-alt
+              = current_user.place
+          - if current_user.website
+            span
+              i.bi.bi-link-45deg
+              = link_to nil, "#{current_user.website}"
+          span
+            i.bi.bi-balloon
+            | 誕生日: 
+            = current_user.birth_date.to_fs(:yeardate_jp)
+          span
+            i.bi.bi-calendar4-week
+            | 2025年1月からXを利用しています
+        div.d-flex
+          div.follow-number-wrapper
+            span.follow-number
+              = current_user.followings.count
+            span.follow-text
+              | フォロー中
+          div.follow-number-wrapper
+            span.follow-number
+              = current_user.followers.count
+            span.follow-text
+              | フォロワー
+      div.profile-tabs
+        ul#myTab.nav.nav-tabs.nav-justified.nav-underline
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'tweet'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#tweet-tab-pane"
+              = link_to 'ツイート', profiles_path(tab: 'tweet'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'like'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#like-tab-pane"
+              = link_to 'いいね', profiles_path(tab: 'like'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'retweet'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#retweet-tab-pane"
+              = link_to 'リツイート', profiles_path(tab: 'retweet'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'comment'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#comment-tab-pane"
+              = link_to 'コメント', profiles_path(tab: 'comment'), class: 'link'
+        
+        #myTabContent.tab-content
+          div class="#{'show active' if params[:tab] == 'tweet'} tab-pane fade" id="tweet-tab-pane"
+            = render partial: 'layouts/post', collection: @tweets, as: "tweet"
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @tweets, :param_name => 'tweet', params: {tab: "tweet"}, theme: 'bootstrap-5'
+          
+          div class="#{'show active' if params[:tab] == 'like'} tab-pane fade" id="like-tab-pane"
+            = render partial: 'layouts/post', collection: @like_tweets, as: "tweet"
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @like_tweets, :param_name => 'like', params: {tab: "like"}, theme: 'bootstrap-5'
+
+          div class="#{'show active' if params[:tab] == 'retweet'} tab-pane fade" id="retweet-tab-pane"
+            = render partial: 'layouts/post', collection: @retweet_tweets, as: "tweet"
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @retweet_tweets, :param_name => 'retweet', params: {tab: "retweet"}, theme: 'bootstrap-5'
+
+          div class="#{'show active' if params[:tab] == 'comment'} tab-pane fade" id="comment-tab-pane"
+            = render partial: 'layouts/post', collection: @comment_tweets, as: "tweet"
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @comment_tweets, :param_name => 'comment', params: {tab: "comment"}, theme: 'bootstrap-5'
+
+  = render 'layouts/aside'
diff --git a/app/views/tweets/_form.html.slim b/app/views/tweets/_form.html.slim
new file mode 100644
index 0000000..9b5b25a
--- /dev/null
+++ b/app/views/tweets/_form.html.slim
@@ -0,0 +1,31 @@
+div.post-section.d-flex.border-bottom
+  div
+    = link_to image_tag(current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"), profiles_path
+  div.w-100
+    = form_with model: tweet do |f|
+      = f.text_area :content, class: "post-content", placeholder: "いまどうしてる？"
+      div.all-reply
+        i.bi.bi-globe-americas.me-1
+        span 全員が返信できます
+      div.d-flex.justify-content-between.pt-2.border-top.pb-2
+        ul.post-icon-list.d-flex
+          li.ms-2
+            div
+              i.bi.bi-card-image
+          li
+            div
+              i.bi.bi-filetype-gif
+          li
+            div
+              i.bi.bi-list-ul
+          li
+            div
+              i.bi.bi-emoji-smile
+          li
+            div
+              i.bi.bi-calendar-event
+          li
+            div
+              i.bi.bi-geo-alt
+        div
+          = f.submit "ポストする", class: "post-btn"
\ No newline at end of file
diff --git a/app/views/tweets/index.html.slim b/app/views/tweets/index.html.slim
index 396af93..2007c3a 100644
--- a/app/views/tweets/index.html.slim
+++ b/app/views/tweets/index.html.slim
@@ -1,53 +1,5 @@
 div.toppage-container
-  nav.nav
-    ul
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-house-door
-          span.icon-name ホーム
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-search
-          span.icon-name 話題を検索
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-bell
-          span.icon-name 通知
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-envelope
-          span.icon-name メッセージ
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-bookmark
-          span.icon-name ブックマーク
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-people
-          span.icon-name コミュニティ
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-lightning
-          span.icon-name 認証済み組織
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-person
-          span.icon-name プロフィール
-      li
-        = button_to "/", class: "nav-btn" do
-          i.bi.bi-three-dots
-          span.icon-name もっと見る
-      li
-        button.btn.post-btn 
-          span ポストする
-      li
-        div.nav-user-info
-          = button_to "/", class: "nav-user-info-btn" do
-            = image_tag current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "user-img"
-            div.nav-user-info-name
-              = current_user.name
-            div.nav-user-info-dots
-              i.bi.bi-three-dots
+  = render 'layouts/sidebar'
   main.main.border-start.border-end
     ul#myTab.nav.nav-tabs.nav-justified.nav-underline
       li.nav-item
@@ -59,151 +11,15 @@ div.toppage-container
 
     #myTabContent.tab-content
       div class="#{'show active' if params[:tab] == 'recommend'} tab-pane fade" id="recommend-tab-pane"
-        div.post-section.d-flex.border-bottom
-          div
-            = image_tag current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
-          div.w-100
-            = form_with model: @tweet do |f|
-              = f.text_area :content, class: "post-content", placeholder: "いまどうしてる？"
-              div.all-reply
-                i.bi.bi-globe-americas.me-1
-                span 全員が返信できます
-              div.d-flex.justify-content-between.pt-2.border-top.pb-2
-                ul.post-icon-list.d-flex
-                  li.ms-2
-                    div
-                      i.bi.bi-card-image
-                  li
-                    div
-                      i.bi.bi-filetype-gif
-                  li
-                    div
-                      i.bi.bi-list-ul
-                  li
-                    div
-                      i.bi.bi-emoji-smile
-                  li
-                    div
-                      i.bi.bi-calendar-event
-                  li
-                    div
-                      i.bi.bi-geo-alt
-                div
-                  = f.submit "ポストする", class: "post-btn"
-        div.post-display-section
-          - @tweets.each do |tweet|
-            div.one-post-display.border-bottom
-              div.d-flex
-                div
-                  = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
-                div.w-100
-                  div.d-flex
-                    span.post-username
-                      = tweet.user.name
-                    span.post-date.me-auto
-                      | ・
-                      = tweet.created_at.to_fs(:datetime_jp)
-                    i.bi.bi-three-dots
-                  div.post-content
-                    span
-                      = tweet.content
-                  div.post-img-wrapper
-                    - if tweet.images.attached?
-                      - tweet.images.each do |image|
-                        = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
-                  div.d-flex.post-display-icon-list
-                    div
-                      i.bi.bi-chat
-                    div
-                      i.bi.bi-arrow-repeat
-                    div
-                      i.bi.bi-heart
-                    div
-                      i.bi.bi-bar-chart-fill
-                    div.d-flex.ms-auto
-                      div
-                        i.bi.bi-bookmark.me-3
-                      div
-                        i.bi.bi-upload
+        = render partial: 'form', locals: {tweet: @tweet}
+        = render partial: 'layouts/post', collection: @tweets, as: "tweet"
         div.paginate.d-flex.justify-content-center.my-3
           = paginate @tweets, :param_name => 'recommend', params: {tab: "recommend"}, theme: 'bootstrap-5'
 
       div class="#{'show active' if params[:tab] == 'follow'} tab-pane fade" id="follow-tab-pane"
-        div.post-section.d-flex.border-bottom
-          div
-            = image_tag current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
-          div.w-100
-            = form_with model: @tweet do |f|
-              = f.text_area :content, class: "post-content", placeholder: "いまどうしてる？"
-              div.all-reply
-                i.bi.bi-globe-americas.me-1
-                span 全員が返信できます
-              div.d-flex.justify-content-between.pt-2.border-top.pb-2
-                ul.post-icon-list.d-flex
-                  li.ms-2
-                    div
-                      i.bi.bi-card-image
-                  li
-                    div
-                      i.bi.bi-filetype-gif
-                  li
-                    div
-                      i.bi.bi-list-ul
-                  li
-                    div
-                      i.bi.bi-emoji-smile
-                  li
-                    div
-                      i.bi.bi-calendar-event
-                  li
-                    div
-                      i.bi.bi-geo-alt
-                div
-                  = f.submit "ポストする", class: "post-btn"
-        div.post-display-section
-          - @following_users_tweets.each do |tweet|
-            div.one-post-display.border-bottom
-              div.d-flex
-                div
-                  = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
-                div.w-100
-                  div.d-flex
-                    span.post-username
-                      = tweet.user.name
-                    span.post-date.me-auto
-                      | ・
-                      = tweet.created_at.to_fs(:datetime_jp)
-                    i.bi.bi-three-dots
-                  div.post-content
-                    span
-                      = tweet.content
-                  div.post-img-wrapper
-                    - if tweet.images.attached?
-                      - tweet.images.each do |image|
-                        = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
-                  div.d-flex.post-display-icon-list
-                    div
-                      i.bi.bi-chat
-                    div
-                      i.bi.bi-arrow-repeat
-                    div
-                      i.bi.bi-heart
-                    div
-                      i.bi.bi-bar-chart-fill
-                    div.d-flex.ms-auto
-                      div
-                        i.bi.bi-bookmark.me-3
-                      div
-                        i.bi.bi-upload
+        = render partial: 'form', locals: {tweet: @tweet}
+        = render partial: 'layouts/post', collection: @following_user_tweets, as: "tweet"
         div.paginate.d-flex.justify-content-center.my-3
-          = paginate @following_users_tweets, :param_name => 'follow', params: {tab: "follow"}, theme: 'bootstrap-5'
+          = paginate @following_user_tweets, :param_name => 'follow', params: {tab: "follow"}, theme: 'bootstrap-5'
 
-  aside.aside
-    div.search
-      input.form-control placeholder="検索" 
-    div.now-find
-      p 「いま」を見つけよう
-    div.recommend-user
-      p おすすめユーザー
-    div
-      
\ No newline at end of file
+  = render 'layouts/aside'
\ No newline at end of file
diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb
index 4a67a04..98266c9 100644
--- a/config/initializers/time_formats.rb
+++ b/config/initializers/time_formats.rb
@@ -1,3 +1,4 @@
 # frozen_string_literal: true
 
-Time::DATE_FORMATS[:datetime_jp] = '%m月%d日'
+Time::DATE_FORMATS[:date_jp] = '%-m月%-d日'
+Date::DATE_FORMATS[:yeardate_jp] = '%Y年%-m月%-d日'
diff --git a/config/routes.rb b/config/routes.rb
index 9f77519..afd7ad3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 Rails.application.routes.draw do
+  # get 'profiles/index'
+  # get 'profiles/edit'
   # get 'tweets/index'
   # get 'tweets/show'
   # get 'tweets/new'
@@ -18,4 +20,5 @@
   root 'tweets#index'
   mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
   resources :tweets
+  resources :profiles, only: %i[index edit]
 end
diff --git a/db/migrate/20250404060007_add_omniauth_to_users.rb b/db/migrate/20250404060007_add_omniauth_to_users.rb
index 2b057c5..230d875 100644
--- a/db/migrate/20250404060007_add_omniauth_to_users.rb
+++ b/db/migrate/20250404060007_add_omniauth_to_users.rb
@@ -2,8 +2,10 @@
 
 class AddOmniauthToUsers < ActiveRecord::Migration[7.0]
   def change
-    add_column :users, :uid, :string,      null: false, default: ''
-    add_column :users, :provider, :string, null: false, default: ''
+    change_table :users, bulk: true do |t|
+      t.string :uid,      null: false, default: ''
+      t.string :provider, null: false, default: ''
+    end
 
     add_index :users, %i[uid provider], unique: true
   end
diff --git a/db/migrate/20250507114656_add_column_to_user.rb b/db/migrate/20250507114656_add_column_to_user.rb
new file mode 100644
index 0000000..d31e481
--- /dev/null
+++ b/db/migrate/20250507114656_add_column_to_user.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddColumnToUser < ActiveRecord::Migration[7.0]
+  def change
+    change_table :users, bulk: true do |t|
+      t.text :self_introduction
+      t.string :place
+      t.text :website
+    end
+  end
+end
diff --git a/db/migrate/20250510083809_create_likes.rb b/db/migrate/20250510083809_create_likes.rb
new file mode 100644
index 0000000..8c8cd84
--- /dev/null
+++ b/db/migrate/20250510083809_create_likes.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateLikes < ActiveRecord::Migration[7.0]
+  def change
+    create_table :likes do |t|
+      t.integer :user_id
+      t.integer :tweet_id
+
+      t.timestamps
+    end
+
+    add_index :likes, %i[user_id tweet_id], unique: true
+  end
+end
diff --git a/db/migrate/20250511195808_create_retweets.rb b/db/migrate/20250511195808_create_retweets.rb
new file mode 100644
index 0000000..da36e40
--- /dev/null
+++ b/db/migrate/20250511195808_create_retweets.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateRetweets < ActiveRecord::Migration[7.0]
+  def change
+    create_table :retweets do |t|
+      t.integer :user_id
+      t.integer :tweet_id
+
+      t.timestamps
+    end
+
+    add_index :retweets, %i[user_id tweet_id], unique: true
+  end
+end
diff --git a/db/migrate/20250512020024_create_comments.rb b/db/migrate/20250512020024_create_comments.rb
new file mode 100644
index 0000000..12e96c2
--- /dev/null
+++ b/db/migrate/20250512020024_create_comments.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class CreateComments < ActiveRecord::Migration[7.0]
+  def change
+    create_table :comments do |t|
+      t.integer :user_id
+      t.integer :tweet_id
+      t.text :content
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e8391b7..9534769 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.0].define(version: 2025_04_19_004826) do
+ActiveRecord::Schema[7.0].define(version: 2025_05_12_020024) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
@@ -42,6 +42,22 @@
     t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
   end
 
+  create_table "comments", force: :cascade do |t|
+    t.integer "user_id"
+    t.integer "tweet_id"
+    t.text "content"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
+  create_table "likes", force: :cascade do |t|
+    t.integer "user_id"
+    t.integer "tweet_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["user_id", "tweet_id"], name: "index_likes_on_user_id_and_tweet_id", unique: true
+  end
+
   create_table "relationships", force: :cascade do |t|
     t.integer "following_id", null: false
     t.integer "follower_id", null: false
@@ -49,6 +65,14 @@
     t.datetime "updated_at", null: false
   end
 
+  create_table "retweets", force: :cascade do |t|
+    t.integer "user_id"
+    t.integer "tweet_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["user_id", "tweet_id"], name: "index_retweets_on_user_id_and_tweet_id", unique: true
+  end
+
   create_table "tasks", force: :cascade do |t|
     t.string "title"
     t.text "description"
@@ -90,6 +114,9 @@
     t.datetime "updated_at", null: false
     t.string "uid", default: "", null: false
     t.string "provider", default: "", null: false
+    t.text "self_introduction"
+    t.string "place"
+    t.text "website"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["email"], name: "index_users_on_email", unique: true
     t.index ["name"], name: "index_users_on_name", unique: true
diff --git a/db/seeds.rb b/db/seeds.rb
index c95c36a..2a01fd5 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -15,10 +15,15 @@
   name: 'ユーザー1',
   phone_number: '111-1111-1111',
   birth_date: '2025-01-01',
-  uid: SecureRandom.uuid
+  uid: SecureRandom.uuid,
+  self_introduction: "中華料理が好きです。\n特にラーメンと餃子が好きです。",
+  place: '大阪',
+  website: 'https://github.com/jin-hatanaka'
 )
 user1.icon_image.attach(io: File.open(Rails.root.join('app/assets/images/icon1.png')),
                         filename: 'icon1.png')
+user1.header_image.attach(io: File.open(Rails.root.join('app/assets/images/header1.png')),
+                          filename: 'header1.png')
 user1.skip_confirmation! # create,save,updateの前に呼び出す必要がある
 user1.save!
 
@@ -49,7 +54,7 @@
 user3.save!
 
 Rails.logger.debug '==================== tweet create ===================='
-user3.tweets.create!(
+tweet1 = user3.tweets.create!(
   content: 'ユーザー3の初めての投稿です。'
 )
 
@@ -79,11 +84,11 @@
 tweet7.images.attach(io: File.open(Rails.root.join('app/assets/images/strawberry_daifuku.png')),
                      filename: 'strawberry_daifuku.png')
 
-user2.tweets.create!(
+tweet8 = user2.tweets.create!(
   content: 'サッカーシューズを買いました。'
 )
 
-user1.tweets.create!(
+tweet9 = user1.tweets.create!(
   content: 'いちごが食べたい。'
 )
 
@@ -102,3 +107,45 @@
 
 follow5 = user3.relationships.build(follower_id: user2.id)
 follow5.save
+
+Rails.logger.debug '==================== like relationship create ===================='
+like1 = user1.likes.build(tweet_id: tweet1.id)
+like1.save!
+
+like2 = user1.likes.build(tweet_id: tweet8.id)
+like2.save!
+
+like3 = user2.likes.build(tweet_id: tweet9.id)
+like3.save!
+
+like4 = user3.likes.build(tweet_id: tweet8.id)
+like4.save!
+
+Rails.logger.debug '==================== retweet relationship create ===================='
+retweet1 = user1.retweets.build(tweet_id: tweet8.id)
+retweet1.save!
+
+retweet2 = user2.retweets.build(tweet_id: tweet7.id)
+retweet2.save!
+
+retweet3 = user3.retweets.build(tweet_id: tweet8.id)
+retweet3.save!
+
+Rails.logger.debug '==================== comment relationship create ===================='
+comment1 = tweet8.comments.build(
+  content: 'かっこいいサッカーシューズですね。'
+)
+comment1.user = user1
+comment1.save!
+
+comment2 = tweet7.comments.build(
+  content: 'おいしそうな苺大福ですね。'
+)
+comment2.user = user2
+comment2.save!
+
+comment3 = tweet9.comments.build(
+  content: 'あまおうが食べたいですね。'
+)
+comment3.user = user3
+comment3.save!
diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb
new file mode 100644
index 0000000..9c782b1
--- /dev/null
+++ b/spec/models/comment_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Comment, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/like_spec.rb b/spec/models/like_spec.rb
new file mode 100644
index 0000000..32c97f6
--- /dev/null
+++ b/spec/models/like_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Like, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/retweet_spec.rb b/spec/models/retweet_spec.rb
new file mode 100644
index 0000000..2c87298
--- /dev/null
+++ b/spec/models/retweet_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Retweet, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/requests/profiles_spec.rb b/spec/requests/profiles_spec.rb
new file mode 100644
index 0000000..7934e5d
--- /dev/null
+++ b/spec/requests/profiles_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Profiles', type: :request do
+  describe 'GET /index' do
+    it 'returns http success' do
+      get '/profiles/index'
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET /edit' do
+    it 'returns http success' do
+      get '/profiles/edit'
+      expect(response).to have_http_status(:success)
+    end
+  end
+end


Reviews:
user: login: yamaryo416
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T01:24:17Z
user: login: yamaryo416
body:  LGTM!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T02:21:02Z

Review comments:
diff_hunk: @@ -0,0 +1,332 @@
+div.toppage-container
+  nav.nav
+    ul
+      li
+        = button_to "/", method: :get, class: "nav-btn" do
+          i.bi.bi-house-door
+          span.icon-name ホーム
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-search
+          span.icon-name 話題を検索
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-bell
+          span.icon-name 通知
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-envelope
+          span.icon-name メッセージ
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-bookmark
+          span.icon-name ブックマーク
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-people
+          span.icon-name コミュニティ
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-lightning
+          span.icon-name 認証済み組織
+      li
+        = button_to profiles_path, method: :get, class: "nav-btn" do
+          i.bi.bi-person
+          span.icon-name プロフィール
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-three-dots
+          span.icon-name もっと見る
path: app/views/profiles/index.html.slim
user: login: yamaryo416
body: サイドバーはパーシャルとして切り出しましょう。

created_at: 2025-05-14T01:21:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,332 @@
+div.toppage-container
+  nav.nav
+    ul
+      li
+        = button_to "/", method: :get, class: "nav-btn" do
+          i.bi.bi-house-door
+          span.icon-name ホーム
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-search
+          span.icon-name 話題を検索
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-bell
+          span.icon-name 通知
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-envelope
+          span.icon-name メッセージ
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-bookmark
+          span.icon-name ブックマーク
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-people
+          span.icon-name コミュニティ
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-lightning
+          span.icon-name 認証済み組織
+      li
+        = button_to profiles_path, method: :get, class: "nav-btn" do
+          i.bi.bi-person
+          span.icon-name プロフィール
+      li
+        = button_to "/", class: "nav-btn" do
+          i.bi.bi-three-dots
+          span.icon-name もっと見る
+      li
+        button.btn.post-btn 
+          span ポストする
+      li
+        div.nav-user-info
+          = button_to "/", class: "nav-user-info-btn" do
+            = image_tag current_user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "user-img"
+            div.nav-user-info-name
+              = current_user.name
+            div.nav-user-info-dots
+              i.bi.bi-three-dots
+  main.main.border-start.border-end
+    div.profile-bar.d-flex
+      div.d-flex.align-items-center
+        = button_to "/", method: :get, class: "back-btn" do
+          i.bi.bi-arrow-left
+      div.ms-4
+        div.profile-bar-username
+          | ユーザー1
+        div.posts-number
+          = current_user.tweets.count
+          | 件のポスト
+    div.profile-contents
+      div.header-img-wrapper
+        - if current_user.header_image.attached?
+          = image_tag current_user.header_image.variant(gravity: :center, resize:"603x200^", crop:"603x200+0+0").processed, class: "header-img"
+        - else
+          div.no-header-img
+      div.profile-user-img-wrapper
+        = image_tag current_user.icon_image.variant(resize_to_fill: [134, 134]).processed, class: "profile-user-img"
+      div.profile-info
+        div.d-flex.justify-content-end
+          = button_to "プロフィールを編集", "/", class: "profile-edit-btn"
+        div.profile-username
+          = current_user.name
+        div.self-introduction
+          = simple_format(current_user.self_introduction)
+        div.profile-others
+          - if current_user.place?
+            span
+              i.bi.bi-geo-alt
+              = current_user.place
+          - if current_user.website
+            span
+              i.bi.bi-link-45deg
+              = link_to nil, "#{current_user.website}"
+          span
+            i.bi.bi-balloon
+            | 誕生日: 
+            = current_user.birth_date.to_fs(:yeardate_jp)
+          span
+            i.bi.bi-calendar4-week
+            | 2025年1月からXを利用しています
+        div.d-flex
+          div.follow-number-wrapper
+            span.follow-number
+              = current_user.followings.count
+            span.follow-text
+              | フォロー中
+          div.follow-number-wrapper
+            span.follow-number
+              = current_user.followers.count
+            span.follow-text
+              | フォロワー
+      div.profile-tabs
+        ul#myTab.nav.nav-tabs.nav-justified.nav-underline
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'tweet'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#tweet-tab-pane"
+              = link_to 'ツイート', profiles_path(tab: 'tweet'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'like'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#like-tab-pane"
+              = link_to 'いいね', profiles_path(tab: 'like'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'retweet'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#retweet-tab-pane"
+              = link_to 'リツイート', profiles_path(tab: 'retweet'), class: 'link'
+          li.nav-item
+            div class="#{'active' if params[:tab] == 'comment'} tab-btn" type="button" data-bs-toggle="tab" data-bs-target="#comment-tab-pane"
+              = link_to 'コメント', profiles_path(tab: 'comment'), class: 'link'
+        
+        #myTabContent.tab-content
+          div class="#{'show active' if params[:tab] == 'tweet'} tab-pane fade" id="tweet-tab-pane"
+            div.post-display-section
+              - @tweets.each do |tweet|
+                div.one-post-display.border-bottom
+                  div.d-flex
+                    div
+                      = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
+                    div.w-100
+                      div.d-flex
+                        span.post-username
+                          = tweet.user.name
+                        span.post-date.me-auto
+                          | ・
+                          = tweet.created_at.to_fs(:date_jp)
+                        i.bi.bi-three-dots
+                      div.post-content
+                        span
+                          = tweet.content
+                      div.post-img-wrapper
+                        - if tweet.images.attached?
+                          - tweet.images.each do |image|
+                            = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
+                      div.d-flex.post-display-icon-list
+                        div
+                          i.bi.bi-chat
+                          span
+                            = tweet.comments.count
+                        div
+                          - if tweet.retweeted_by?(current_user)
+                            i.bi.bi-arrow-repeat.active
+                          - else
+                            i.bi.bi-arrow-repeat
+                          span class="#{'retweeted' if tweet.retweeted_by?(current_user)}"
+                            = tweet.retweets.count
+                        div.d-flex.align-items-center
+                          - if tweet.liked_by?(current_user)
+                            i.bi.bi-heart-fill
+                          - else
+                            i.bi.bi-heart
+                          span class="#{'liked' if tweet.liked_by?(current_user)}"
+                            = tweet.likes.count
+                        div
+                          i.bi.bi-bar-chart-fill
+                        div.d-flex.ms-auto
+                          div
+                            i.bi.bi-bookmark.me-3
+                          div
+                            i.bi.bi-upload
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @tweets, :param_name => 'tweet', params: {tab: "tweet"}, theme: 'bootstrap-5'
+          
+          div class="#{'show active' if params[:tab] == 'like'} tab-pane fade" id="like-tab-pane"
+            div.post-display-section
+              - @like_tweets.each do |tweet|
+                div.one-post-display.border-bottom
+                  div.d-flex
+                    div
+                      = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
+                    div.w-100
+                      div.d-flex
+                        span.post-username
+                          = tweet.user.name
+                        span.post-date.me-auto
+                          | ・
+                          = tweet.created_at.to_fs(:date_jp)
+                        i.bi.bi-three-dots
+                      div.post-content
+                        span
+                          = tweet.content
+                      div.post-img-wrapper
+                        - if tweet.images.attached?
+                          - tweet.images.each do |image|
+                            = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
+                      div.d-flex.post-display-icon-list
+                        div
+                          i.bi.bi-chat
+                          span
+                            = tweet.comments.count
+                        div
+                          - if tweet.retweeted_by?(current_user)
+                            i.bi.bi-arrow-repeat.active
+                          - else
+                            i.bi.bi-arrow-repeat
+                          span class="#{'retweeted' if tweet.retweeted_by?(current_user)}"
+                            = tweet.retweets.count
+                        div.d-flex.align-items-center
+                          - if tweet.liked_by?(current_user)
+                            i.bi.bi-heart-fill
+                          - else
+                            i.bi.bi-heart
+                          span class="#{'liked' if tweet.liked_by?(current_user)}"
+                            = tweet.likes.count
+                        div
+                          i.bi.bi-bar-chart-fill
+                        div.d-flex.ms-auto
+                          div
+                            i.bi.bi-bookmark.me-3
+                          div
+                            i.bi.bi-upload
+            div.paginate.d-flex.justify-content-center.my-3
+              = paginate @like_tweets, :param_name => 'like', params: {tab: "like"}, theme: 'bootstrap-5'
+
+          div class="#{'show active' if params[:tab] == 'retweet'} tab-pane fade" id="retweet-tab-pane"
+            div.post-display-section
+              - @retweet_tweets.each do |tweet|
+                div.one-post-display.border-bottom
+                  div.d-flex
+                    div
+                      = image_tag tweet.user.icon_image.variant(resize_to_fill: [40, 40]).processed, class: "post-user-img rounded-circle"
+                    div.w-100
+                      div.d-flex
+                        span.post-username
+                          = tweet.user.name
+                        span.post-date.me-auto
+                          | ・
+                          = tweet.created_at.to_fs(:date_jp)
+                        i.bi.bi-three-dots
+                      div.post-content
+                        span
+                          = tweet.content
+                      div.post-img-wrapper
+                        - if tweet.images.attached?
+                          - tweet.images.each do |image|
+                            = image_tag image.variant(gravity: :center, resize:"500x270^", crop:"500x270+0+0").processed, class: "post-img"
+                      div.d-flex.post-display-icon-list
+                        div
+                          i.bi.bi-chat
+                          span
+                            = tweet.comments.count
+                        div
path: app/views/profiles/index.html.slim
user: login: yamaryo416
body: ツイートの1カードをパーシャルとして切り出しましょう。
created_at: 2025-05-14T01:22:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateLikes < ActiveRecord::Migration[7.0]
+  def change
+    create_table :likes do |t|
+      t.integer :user_id
+      t.integer :tweet_id
+
+      t.timestamps
path: db/migrate/20250510083809_create_likes.rb
user: login: yamaryo416
body: `user_id`と、`tweet_id`の組み合わせが一意になるように制約を入れましょう。
created_at: 2025-05-14T01:23:28Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateRetweets < ActiveRecord::Migration[7.0]
+  def change
+    create_table :retweets do |t|
+      t.integer :user_id
+      t.integer :tweet_id
+
+      t.timestamps
path: db/migrate/20250511195808_create_retweets.rb
user: login: yamaryo416
body: `user_id`と、`tweet_id`の組み合わせが一意になるように制約を入れましょう。
created_at: 2025-05-14T01:23:59Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-14T21:11:09Z
body: <!--
  1 Error: reviewersを指定して下さい
  0 Warnings
  0 Messages
  0 Markdowns
-->
<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true" data-kind="Error">
          1 Error
      </th>
     </tr>
  </thead>
  <tbody>
    <tr>
      <td>:no_entry_sign:</td>
      <td data-sticky="false">reviewersを指定して下さい</td>
    </tr>
  </tbody>
</table>

<p align="right" data-meta="generated_by_danger">
  Generated by :no_entry_sign: <a href="https://danger.systems/">Danger</a>
</p>

author_association: NONE
user: login: jin-hatanaka
created_at: 2025-05-14T21:18:51Z
body: ご確認ありがとうございます！
上記について修正しましたのでご確認お願いいたします。
author_association: OWNER