diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index adcd5c941a7e8cc537098a935c25813106b579ab..2461040324f04f00cae09b1e953b5e58d2feb649 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -3100,7 +3100,6 @@ Layout/LineLength:
     - 'ee/spec/views/registrations/welcome/show.html.haml_spec.rb'
     - 'ee/spec/views/shared/_mirror_status.html.haml_spec.rb'
     - 'ee/spec/views/shared/_namespace_user_cap_reached_alert.html.haml_spec.rb'
-    - 'ee/spec/views/shared/_user_over_limit_free_plan_alert.html.haml_spec.rb'
     - 'ee/spec/views/shared/access_tokens/_table.html.haml_spec.rb'
     - 'ee/spec/views/shared/billings/_eoa_bronze_plan_banner.html.haml_spec.rb'
     - 'ee/spec/views/shared/billings/_trial_status.html.haml_spec.rb'
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 9aa626724a822ddd8d7c401843b19c6409c6a05c..926e7345edb6b848a85b4d92ad755481bde07bdf 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,7 +1,7 @@
 - add_page_specific_style 'page_bundles/members'
 - page_title _('Group members')
 
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
+= render_if_exists 'shared/free_user_cap_alert', source: @group
 
 .row.gl-mt-3
   .col-lg-12
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 7bbc2f839f77e726848eddc7cc998a52ac355f15..9372210331619157255a92164abdd6c89cbc0b97 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -7,7 +7,7 @@
   = render_if_exists 'shared/thanks_for_purchase_banner', plan_title: plan_title, quantity: params[:purchased_quantity].to_i
 
 = render_if_exists 'shared/qrtly_reconciliation_alert', group: @group
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @group
+= render_if_exists 'shared/free_user_cap_alert', source: @group
 = render_if_exists 'shared/minute_limit_banner', namespace: @group
 
 - if show_invite_banner?(@group)
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 3c4b612f33f2c71029a1646f9ab4fe1c11b262a8..66f671f8073c169d4de695d3486c3ea1b7993996 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -20,7 +20,7 @@
       = dispensable_render_if_exists "shared/namespace_user_cap_reached_alert"
       = dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
       = yield :page_level_alert
-      = yield :user_over_limit_free_plan_alert
+      = yield :free_user_cap_alert
       = yield :group_invite_members_banner
       - unless @hide_breadcrumbs
         = render "layouts/nav/breadcrumbs"
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index b2338fa6c554aad04ea4282dbc948c11c18c878e..e8c107b9383c7b37325478867dc3c1fb3abcd3ad 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -3,7 +3,8 @@
 - escaped_default_branch_name = default_branch_name.shellescape
 - @skip_current_level_breadcrumb = true
 
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+= render_if_exists 'projects/free_user_cap_alert', project: @project
+
 = render partial: 'flash_messages', locals: { project: @project }
 
 = render "home_panel"
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index 1331ed243072f1ae4a758f7a64238cde340a1d1f..a8a30d730003166490c8acf662586a2472ae8d11 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -1,7 +1,7 @@
 - page_title _('No repository')
 - @skip_current_level_breadcrumb = true
 
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+= render_if_exists 'projects/free_user_cap_alert', project: @project
 
 %h2.gl-display-flex
   .gl-display-flex.gl-align-items-center.gl-justify-content-center
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 298c2074062db50629eb30f163b727bfe216fea9..d0f88f00e2c6b8028e73a27a4a12ac37b89c4c0e 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,7 +1,7 @@
 - add_page_specific_style 'page_bundles/members'
 - page_title _("Members")
 
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+= render_if_exists 'projects/free_user_cap_alert', project: @project
 
 .row.gl-mt-3
   .col-lg-12
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 1934f293b0f06652858d7c880124279201f8c4f2..12983031388a0d6fbb950b8400ceaebe4947079e 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,7 +6,7 @@
 = content_for :meta_tags do
   = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
 
-= render_if_exists 'shared/user_over_limit_free_plan_alert', source: @project
+= render_if_exists 'projects/free_user_cap_alert', project: @project
 = render_if_exists 'shared/minute_limit_banner', namespace: @project
 = render partial: 'flash_messages', locals: { project: @project }
 
diff --git a/ee/app/components/namespaces/free_user_cap_alert_component.html.haml b/ee/app/components/namespaces/free_user_cap/alert_component.html.haml
similarity index 100%
rename from ee/app/components/namespaces/free_user_cap_alert_component.html.haml
rename to ee/app/components/namespaces/free_user_cap/alert_component.html.haml
diff --git a/ee/app/components/namespaces/free_user_cap/alert_component.rb b/ee/app/components/namespaces/free_user_cap/alert_component.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7a5346d93e142ba8e9f63913611af3a7271edb59
--- /dev/null
+++ b/ee/app/components/namespaces/free_user_cap/alert_component.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Namespaces
+  module FreeUserCap
+    class AlertComponent < ViewComponent::Base
+      # @param [Namespace or Group] namespace
+      # @param [User] user
+      # @param [String] content_class
+      def initialize(namespace:, user:, content_class:)
+        @namespace = namespace
+        @user = user
+        @content_class = content_class
+      end
+
+      private
+
+      USER_REACHED_LIMIT_FREE_PLAN_ALERT = 'user_reached_limit_free_plan_alert'
+
+      attr_reader :namespace, :user, :content_class
+
+      def render?
+        return false unless user
+        return false if dismissed?
+        return false unless Ability.allowed?(user, :owner_access, namespace)
+
+        breached_cap_limit?
+      end
+
+      def breached_cap_limit?
+        ::Namespaces::FreeUserCap::Standard.new(namespace).reached_limit?
+      end
+
+      def variant
+        :warning
+      end
+
+      def dismissed?
+        user.dismissed_callout_for_group?(feature_name: feature_name,
+                                          group: namespace,
+                                          ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
+      end
+
+      def ignore_dismissal_earlier_than
+        nil
+      end
+
+      def alert_data
+        base_alert_data.merge(dismiss_endpoint: group_callouts_path, group_id: namespace.id)
+      end
+
+      def base_alert_data
+        {
+          track_action: 'render',
+          track_label: 'user_limit_banner',
+          feature_id: feature_name,
+          testid: 'user-over-limit-free-plan-alert'
+        }
+      end
+
+      def feature_name
+        USER_REACHED_LIMIT_FREE_PLAN_ALERT
+      end
+
+      def close_button_data
+        {
+          track_action: 'dismiss_banner',
+          track_label: 'user_limit_banner',
+          testid: 'user-over-limit-free-plan-dismiss'
+        }
+      end
+
+      def alert_attributes
+        {
+          title: _("Looks like you've reached your %{free_limit} member limit for " \
+                   "%{strong_start}%{namespace_name}%{strong_end}").html_safe % {
+            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
+            strong_start: "<strong>".html_safe,
+            strong_end: "</strong>".html_safe,
+            namespace_name: namespace.name
+          },
+          body: _("You can't add any more, but you can manage your existing members, for example, " \
+                  "by removing inactive members and replacing them with new members. To get more " \
+                  "members an owner of this namespace can start a trial or upgrade to a paid tier."),
+          primary_cta: namespace_primary_cta,
+          secondary_cta: namespace_secondary_cta
+        }
+      end
+
+      def namespace_primary_cta
+        link_to _('Manage members'),
+                group_usage_quotas_path(namespace),
+                class: 'btn gl-alert-action btn-info btn-md gl-button',
+                data: {
+                  track_action: 'click_button',
+                  track_label: 'manage_members',
+                  testid: 'user-over-limit-primary-cta'
+                }
+      end
+
+      def namespace_secondary_cta
+        link_to _('Explore paid plans'),
+                group_billings_path(namespace),
+                class: 'btn gl-alert-action btn-default btn-md gl-button',
+                data: { track_action: 'click_button',
+                        track_label: 'explore_paid_plans',
+                        testid: 'user-over-limit-secondary-cta' }
+      end
+
+      def link_end
+        '</a>'.html_safe
+      end
+    end
+  end
+end
diff --git a/ee/app/components/namespaces/free_user_cap/personable.rb b/ee/app/components/namespaces/free_user_cap/personable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d41b418020782cbcd5538b8f2158eb2955fd5e3d
--- /dev/null
+++ b/ee/app/components/namespaces/free_user_cap/personable.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Namespaces
+  module FreeUserCap
+    module Personable
+      extend ActiveSupport::Concern
+
+      private
+
+      def dismissed?
+        user.dismissed_callout?(feature_name: feature_name,
+                                ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
+      end
+
+      def alert_data
+        base_alert_data.merge(dismiss_endpoint: callouts_path)
+      end
+
+      def personal_primary_cta
+        link_to _('View all personal projects'),
+                user_projects_path(user.username),
+                class: 'btn gl-alert-action btn-info btn-md gl-button',
+                data: {
+                  track_action: 'click_button',
+                  track_label: 'view_personal_projects',
+                  testid: 'user-over-limit-primary-cta'
+                }
+      end
+
+      def move_link_start
+        '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: move_url }
+      end
+
+      def move_url
+        help_page_path('user/project/settings/index', anchor: 'transferring-an-existing-project-into-another-namespace')
+      end
+    end
+  end
+end
diff --git a/ee/app/components/namespaces/free_user_cap/personal_alert_component.rb b/ee/app/components/namespaces/free_user_cap/personal_alert_component.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6b513c9e1b073ec0213d1c88af90e0c7bc261717
--- /dev/null
+++ b/ee/app/components/namespaces/free_user_cap/personal_alert_component.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Namespaces
+  module FreeUserCap
+    class PersonalAlertComponent < AlertComponent
+      include Personable
+
+      def alert_attributes
+        {
+          title: _("You've reached your %{free_limit} member limit across all of your personal projects") % {
+            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT
+          },
+          body: _('You can have a maximum of %{free_limit} unique members across all of your personal projects. ' \
+                'To view and manage members, check the members page for each project in your namespace. ' \
+                'We recommend you %{move_link_start}move your projects to a group%{move_link_end} so you can ' \
+                'easily manage users and features.').html_safe % {
+            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
+            move_link_start: move_link_start,
+            move_link_end: link_end
+          },
+          primary_cta: personal_primary_cta
+        }
+      end
+    end
+  end
+end
diff --git a/ee/app/components/namespaces/free_user_cap/personal_preview_alert_component.rb b/ee/app/components/namespaces/free_user_cap/personal_preview_alert_component.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a8d9baa4c950e1e6012720a89205fe5e2aa4665b
--- /dev/null
+++ b/ee/app/components/namespaces/free_user_cap/personal_preview_alert_component.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Namespaces
+  module FreeUserCap
+    class PersonalPreviewAlertComponent < PreviewAlertComponent
+      include Personable
+
+      private
+
+      def alert_attributes
+        {
+          title: _('From June 22, 2022 (GitLab 15.1), you can have a maximum of %{free_limit} unique members ' \
+                 'across all of your personal projects') % { free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT },
+          body: _('You currently have more than %{free_limit} members across all your personal projects. ' \
+                'From June 22, 2022, the %{free_limit} most recently active members will remain active, ' \
+                'and the remaining members will get a %{link_start}status of Over limit%{link_end} and lose access. ' \
+                'To view and manage members, check the members page for each project in your namespace. ' \
+                'We recommend you %{move_link_start}move your project to a group%{move_link_end} so you can easily ' \
+                'manage users and features.').html_safe % {
+            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
+            link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: BLOG_URL },
+            link_end: link_end,
+            move_link_start: move_link_start,
+            move_link_end: link_end
+          },
+          primary_cta: personal_primary_cta
+        }
+      end
+    end
+  end
+end
diff --git a/ee/app/components/namespaces/free_user_cap/preview_alert_component.rb b/ee/app/components/namespaces/free_user_cap/preview_alert_component.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48d99c36e0802e9aa5e2474089ffb7f7e1185607
--- /dev/null
+++ b/ee/app/components/namespaces/free_user_cap/preview_alert_component.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Namespaces
+  module FreeUserCap
+    class PreviewAlertComponent < AlertComponent
+      private
+
+      PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT = 'preview_user_over_limit_free_plan_alert'
+      IGNORE_DISMISSAL_EARLIER_THAN = 14.days.ago
+      BLOG_URL = 'https://about.gitlab.com/blog/2022/03/24/efficient-free-tier'
+
+      def breached_cap_limit?
+        ::Namespaces::FreeUserCap::Preview.new(namespace).over_limit?
+      end
+
+      def variant
+        :info
+      end
+
+      def ignore_dismissal_earlier_than
+        IGNORE_DISMISSAL_EARLIER_THAN
+      end
+
+      def feature_name
+        PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT
+      end
+
+      def alert_attributes
+        {
+          title: _('From June 22, 2022 (GitLab 15.1), free personal namespaces and top-level groups will be limited ' \
+                 'to %{free_limit} members') % { free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT },
+          body: _('Your %{doc_link_start}namespace%{doc_link_end}, %{strong_start}%{namespace_name}%{strong_end} ' \
+                'has more than %{free_limit} members. From June 22, 2022, it will be limited to %{free_limit}, ' \
+                'and the remaining members will get a %{link_start}status of Over limit%{link_end} and lose ' \
+                'access to the namespace. You can go to the Usage Quotas page to manage which %{free_limit} ' \
+                'members will remain in your namespace. To get more members, an owner can start a trial or upgrade ' \
+                'to a paid tier.').html_safe % {
+            namespace_name: namespace.name,
+            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
+            doc_link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % {
+              url: help_page_path('user/group/index', anchor: 'namespaces')
+            },
+            doc_link_end: link_end,
+            strong_start: "<strong>".html_safe,
+            strong_end: "</strong>".html_safe,
+            link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: BLOG_URL },
+            link_end: link_end
+          },
+          primary_cta: namespace_primary_cta,
+          secondary_cta: namespace_secondary_cta
+        }
+      end
+    end
+  end
+end
diff --git a/ee/app/components/namespaces/free_user_cap_alert_component.rb b/ee/app/components/namespaces/free_user_cap_alert_component.rb
deleted file mode 100644
index 985582bc3a68d64a42e7daa6ad005e3d1fc8cd8a..0000000000000000000000000000000000000000
--- a/ee/app/components/namespaces/free_user_cap_alert_component.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# frozen_string_literal: true
-
-module Namespaces
-  class FreeUserCapAlertComponent < ViewComponent::Base
-    # @param [Namespace or Group] namespace
-    # @param [User] user
-    # @param [String] content_class
-    def initialize(namespace:, user:, content_class:)
-      @namespace = namespace
-      @user = user
-      @content_class = content_class
-    end
-
-    private
-
-    USER_REACHED_LIMIT_FREE_PLAN_ALERT = 'user_reached_limit_free_plan_alert'
-
-    attr_reader :namespace, :user, :content_class
-
-    def render?
-      return false unless user
-      return false if dismissed?
-      return false unless Ability.allowed?(user, :owner_access, namespace)
-
-      breached_cap_limit?
-    end
-
-    def breached_cap_limit?
-      ::Namespaces::FreeUserCap::Standard.new(namespace).reached_limit?
-    end
-
-    def variant
-      :warning
-    end
-
-    def dismissed?
-      if namespace.user_namespace?
-        user.dismissed_callout?(feature_name: feature_name,
-                                ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
-      else
-        user.dismissed_callout_for_group?(feature_name: feature_name,
-                                          group: namespace,
-                                          ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
-      end
-    end
-
-    def ignore_dismissal_earlier_than
-      nil
-    end
-
-    def alert_data
-      base_data = {
-        track_action: 'render',
-        track_label: 'user_limit_banner',
-        feature_id: feature_name,
-        testid: 'user-over-limit-free-plan-alert'
-      }
-
-      if namespace.user_namespace?
-        base_data.merge(dismiss_endpoint: callouts_path)
-      else
-        base_data.merge(dismiss_endpoint: group_callouts_path, group_id: namespace.id)
-      end
-    end
-
-    def feature_name
-      USER_REACHED_LIMIT_FREE_PLAN_ALERT
-    end
-
-    def close_button_data
-      {
-        track_action: 'dismiss_banner',
-        track_label: 'user_limit_banner',
-        testid: 'user-over-limit-free-plan-dismiss'
-      }
-    end
-
-    def alert_attributes
-      if namespace.user_namespace?
-        {
-          title: _("You've reached your %{free_limit} member limit across all your personal projects") % {
-            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT
-          },
-          body: _('You can have a maximum of %{free_limit} unique members across all of your personal projects. ' \
-                'To view and manage members, check the members page for each project in your namespace. ' \
-                'We recommend you %{move_link_start}move your projects to a group%{move_link_end} so you can ' \
-                'easily manage users and features.').html_safe % {
-            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
-            move_link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % {
-              url: move_url
-            },
-            move_link_end: '</a>'.html_safe
-          },
-          primary_cta: user_namespace_primary_cta
-        }
-      else
-        {
-          title: _("Looks like you've reached your %{free_limit} member limit for " \
-                   "%{strong_start}%{namespace_name}%{strong_end}").html_safe % {
-            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
-            strong_start: "<strong>".html_safe,
-            strong_end: "</strong>".html_safe,
-            namespace_name: namespace.name
-          },
-          body: _("You can't add any more, but you can manage your existing members, for example, " \
-                  "by removing inactive members and replacing them with new members. To get more " \
-                  "members an owner of this namespace can start a trial or upgrade to a paid tier."),
-          primary_cta: namespace_primary_cta,
-          secondary_cta: namespace_secondary_cta
-        }
-      end
-    end
-
-    def user_namespace_primary_cta
-      link_to _('View all personal projects'),
-              user_projects_path(user.username),
-              class: 'btn gl-alert-action btn-info btn-md gl-button',
-              data: {
-                track_action: 'click_button',
-                track_label: 'view_personal_projects',
-                testid: 'user-over-limit-primary-cta'
-              }
-    end
-
-    def namespace_primary_cta
-      link_to _('Manage members'),
-              group_usage_quotas_path(namespace),
-              class: 'btn gl-alert-action btn-info btn-md gl-button',
-              data: {
-                track_action: 'click_button',
-                track_label: 'manage_members',
-                testid: 'user-over-limit-primary-cta'
-              }
-    end
-
-    def namespace_secondary_cta
-      link_to _('Explore paid plans'),
-              group_billings_path(namespace),
-              class: 'btn gl-alert-action btn-default btn-md gl-button',
-              data: { track_action: 'click_button',
-                      track_label: 'explore_paid_plans',
-                      testid: 'user-over-limit-secondary-cta' }
-    end
-
-    def move_url
-      help_page_path('user/project/settings/index', anchor: 'transferring-an-existing-project-into-another-namespace')
-    end
-  end
-end
diff --git a/ee/app/components/namespaces/preview_free_user_cap_alert_component.rb b/ee/app/components/namespaces/preview_free_user_cap_alert_component.rb
deleted file mode 100644
index b789f11648a8db452e232ef63d5940fe66dd6e97..0000000000000000000000000000000000000000
--- a/ee/app/components/namespaces/preview_free_user_cap_alert_component.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-module Namespaces
-  class PreviewFreeUserCapAlertComponent < FreeUserCapAlertComponent
-    private
-
-    PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT = 'preview_user_over_limit_free_plan_alert'
-    IGNORE_DISMISSAL_EARLIER_THAN = 14.days.ago
-    BLOG_URL = 'https://about.gitlab.com/blog/2022/03/24/efficient-free-tier'
-
-    def breached_cap_limit?
-      ::Namespaces::FreeUserCap::Preview.new(namespace).over_limit?
-    end
-
-    def variant
-      :info
-    end
-
-    def ignore_dismissal_earlier_than
-      IGNORE_DISMISSAL_EARLIER_THAN
-    end
-
-    def feature_name
-      PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT
-    end
-
-    def alert_attributes
-      link_end = '</a>'.html_safe
-
-      if namespace.user_namespace?
-        {
-          title: _('From June 22, 2022 (GitLab 15.1), you can have a maximum of %{free_limit} unique members ' \
-                 'across all of your personal projects') % { free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT },
-          body: _('You currently have more than %{free_limit} members across all your personal projects. ' \
-                'From June 22, 2022, the %{free_limit} most recently active members will remain active, ' \
-                'and the remaining members will get a %{link_start}status of Over limit%{link_end} and lose access. ' \
-                'To view and manage members, check the members page for each project in your namespace. ' \
-                'We recommend you %{move_link_start}move your project to a group%{move_link_end} so you can easily ' \
-                'manage users and features.').html_safe % {
-            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
-            link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: BLOG_URL },
-            link_end: link_end,
-            move_link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % {
-              url: move_url
-            },
-            move_link_end: link_end
-          },
-          primary_cta: user_namespace_primary_cta
-        }
-      else
-        {
-          title: _('From June 22, 2022 (GitLab 15.1), free personal namespaces and top-level groups will be limited ' \
-                 'to %{free_limit} members') % { free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT },
-          body: _('Your %{doc_link_start}namespace%{doc_link_end}, %{strong_start}%{namespace_name}%{strong_end} ' \
-                'has more than %{free_limit} members. From June 22, 2022, it will be limited to %{free_limit}, ' \
-                'and the remaining members will get a %{link_start}status of Over limit%{link_end} and lose ' \
-                'access to the namespace. You can go to the Usage Quotas page to manage which %{free_limit} ' \
-                'members will remain in your namespace. To get more members, an owner can start a trial or upgrade ' \
-                'to a paid tier.').html_safe % {
-            namespace_name: namespace.name,
-            free_limit: ::Namespaces::FreeUserCap::FREE_USER_LIMIT,
-            doc_link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % {
-              url: help_page_path('user/group/index', anchor: 'namespaces')
-            },
-            doc_link_end: link_end,
-            strong_start: "<strong>".html_safe,
-            strong_end: "</strong>".html_safe,
-            link_start: '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: BLOG_URL },
-            link_end: link_end
-          },
-          primary_cta: namespace_primary_cta,
-          secondary_cta: namespace_secondary_cta
-        }
-      end
-    end
-  end
-end
diff --git a/ee/app/views/projects/_free_user_cap_alert.html.haml b/ee/app/views/projects/_free_user_cap_alert.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..823262d093f733bc19d839a7edd57e5d52e65143
--- /dev/null
+++ b/ee/app/views/projects/_free_user_cap_alert.html.haml
@@ -0,0 +1,4 @@
+- if project.personal?
+  = render 'shared/personal_free_user_cap_alert', source: project
+- else
+  = render 'shared/free_user_cap_alert', source: project
diff --git a/ee/app/views/shared/_free_user_cap_alert.html.haml b/ee/app/views/shared/_free_user_cap_alert.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..72348e9d1eb94dbb2b34a3b93a7fb86f760f850d
--- /dev/null
+++ b/ee/app/views/shared/_free_user_cap_alert.html.haml
@@ -0,0 +1,9 @@
+- content_for :free_user_cap_alert do
+  - if ::Namespaces::FreeUserCap::Standard.new(source.root_ancestor).feature_enabled?
+    = render Namespaces::FreeUserCap::AlertComponent.new(namespace: source.root_ancestor,
+      user: current_user,
+      content_class: @content_class)
+  - else
+    = render Namespaces::FreeUserCap::PreviewAlertComponent.new(namespace: source.root_ancestor,
+      user: current_user,
+      content_class: @content_class)
diff --git a/ee/app/views/shared/_personal_free_user_cap_alert.html.haml b/ee/app/views/shared/_personal_free_user_cap_alert.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..10bfba3a8bb740594de15244f11d1f00c7491826
--- /dev/null
+++ b/ee/app/views/shared/_personal_free_user_cap_alert.html.haml
@@ -0,0 +1,9 @@
+- content_for :free_user_cap_alert do
+  - if ::Namespaces::FreeUserCap::Standard.new(source.root_ancestor).feature_enabled?
+    = render Namespaces::FreeUserCap::PersonalAlertComponent.new(namespace: source.root_ancestor,
+      user: current_user,
+      content_class: @content_class)
+  - else
+    = render Namespaces::FreeUserCap::PersonalPreviewAlertComponent.new(namespace: source.root_ancestor,
+      user: current_user,
+      content_class: @content_class)
diff --git a/ee/app/views/shared/_user_over_limit_free_plan_alert.html.haml b/ee/app/views/shared/_user_over_limit_free_plan_alert.html.haml
deleted file mode 100644
index 6a4f637db9439e914d0aec25eb8b6cebee63c031..0000000000000000000000000000000000000000
--- a/ee/app/views/shared/_user_over_limit_free_plan_alert.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- content_for :user_over_limit_free_plan_alert do
-  - if ::Namespaces::FreeUserCap::Standard.new(source.root_ancestor).feature_enabled?
-    = render Namespaces::FreeUserCapAlertComponent.new(namespace: source.root_ancestor,
-      user: current_user,
-      content_class: @content_class)
-  - else
-    = render Namespaces::PreviewFreeUserCapAlertComponent.new(namespace: source.root_ancestor,
-      user: current_user,
-      content_class: @content_class)
diff --git a/ee/spec/components/namespaces/free_user_cap/alert_component_spec.rb b/ee/spec/components/namespaces/free_user_cap/alert_component_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aa0a828f8636065579b6dd8099e9c29d9aca39ba
--- /dev/null
+++ b/ee/spec/components/namespaces/free_user_cap/alert_component_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+RSpec.describe Namespaces::FreeUserCap::AlertComponent, :saas, :aggregate_failures, type: :component do
+  let_it_be(:namespace, reload: true) { create(:group) }
+  let_it_be(:user, refind: true) { create(:user) }
+  let_it_be(:content_class) { '_content_class_' }
+
+  let(:free_user_cap_reached_limit?) { true }
+  let(:title) { "Looks like you've reached your #{::Namespaces::FreeUserCap::FREE_USER_LIMIT} member limit" }
+
+  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
+
+  context 'when user is authorized to see alert' do
+    before do
+      namespace.add_owner(user)
+
+      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
+        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
+      end
+    end
+
+    context 'when limit has been reached' do
+      it 'has content for the alert' do
+        render_inline(component)
+
+        expect(rendered_component).to have_selector(".#{content_class}")
+        expect(rendered_component).to have_content(title)
+        expect(rendered_component).to have_link('Manage members', href: group_usage_quotas_path(namespace))
+        expect(rendered_component).to have_link('Explore paid plans', href: group_billings_path(namespace))
+        expect(rendered_component)
+          .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
+                         "[data-dismiss-endpoint='#{group_callouts_path}']" \
+                         "[data-feature-id='#{described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT}']" \
+                         "[data-group-id='#{namespace.id}']")
+      end
+
+      it 'renders all the expected tracking items' do
+        render_inline(component)
+
+        expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
+                                                 '[data-track-action="dismiss_banner"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="manage_members"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-secondary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="explore_paid_plans"]')
+      end
+
+      context 'when alert has been dismissed' do
+        context 'with a fresh dismissal' do
+          before do
+            create(:group_callout,
+                   user: user,
+                   group: namespace,
+                   feature_name: described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT)
+          end
+
+          it 'does not render the alert' do
+            render_inline(component)
+
+            expect(rendered_component).not_to have_content(title)
+          end
+        end
+      end
+    end
+
+    context 'when limit has not been reached' do
+      let(:free_user_cap_reached_limit?) { false }
+
+      it 'does not render the alert' do
+        render_inline(component)
+
+        expect(rendered_component).not_to have_content(title)
+      end
+    end
+  end
+
+  context 'when user is not authorized to see alert' do
+    before do
+      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
+        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
+      end
+    end
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user does not exist' do
+    let_it_be(:user) { nil }
+
+    before do
+      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
+        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
+      end
+    end
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+end
diff --git a/ee/spec/components/namespaces/free_user_cap/personal_alert_component_spec.rb b/ee/spec/components/namespaces/free_user_cap/personal_alert_component_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5cdd50ffcf1e3910bbcbe1bc8fb3a7087acb967e
--- /dev/null
+++ b/ee/spec/components/namespaces/free_user_cap/personal_alert_component_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+RSpec.describe Namespaces::FreeUserCap::PersonalAlertComponent, :saas, :aggregate_failures, type: :component do
+  let_it_be(:user, refind: true) { create(:user) }
+  let_it_be(:namespace) { user.namespace }
+  let_it_be(:content_class) { '_content_class_' }
+
+  let(:free_user_cap_reached_limit?) { true }
+  let(:title) { "You've reached your #{::Namespaces::FreeUserCap::FREE_USER_LIMIT} member limit" }
+
+  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
+
+  before do
+    allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
+      allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
+    end
+  end
+
+  context 'when limit has been reached' do
+    it 'has content for the alert' do
+      render_inline(component)
+
+      expect(rendered_component).to have_selector(".#{content_class}")
+      expect(rendered_component).to have_content(title)
+      expect(rendered_component).to have_link('View all personal projects', href: user_projects_path(user.username))
+      expect(rendered_component).to have_link('move your projects to a group')
+      expect(rendered_component)
+        .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
+                         "[data-dismiss-endpoint='#{callouts_path}']" \
+                         "[data-feature-id='#{described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT}']")
+      expect(rendered_component).not_to have_css('[data-testid="user-over-limit-secondary-cta"]')
+    end
+
+    it 'renders all the expected tracking items' do
+      render_inline(component)
+
+      expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+      expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
+                                                 '[data-track-action="dismiss_banner"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+      expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="view_personal_projects"]')
+    end
+
+    context 'when alert has been dismissed' do
+      context 'with a fresh dismissal' do
+        before do
+          create(:callout, user: user, feature_name: described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT)
+        end
+
+        it 'does not render the alert' do
+          render_inline(component)
+
+          expect(rendered_component).not_to have_content(title)
+        end
+      end
+    end
+  end
+
+  context 'when limit has not been reached' do
+    let(:free_user_cap_reached_limit?) { false }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user is not authorized to see alert' do
+    let_it_be(:user) { create(:user) }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user does not exist' do
+    let_it_be(:user) { nil }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+end
diff --git a/ee/spec/components/namespaces/free_user_cap/personal_preview_alert_component_spec.rb b/ee/spec/components/namespaces/free_user_cap/personal_preview_alert_component_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b296677e3b4805b8999a5e6c9818fe49d9f2d966
--- /dev/null
+++ b/ee/spec/components/namespaces/free_user_cap/personal_preview_alert_component_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+RSpec.describe Namespaces::FreeUserCap::PersonalPreviewAlertComponent, :saas, :aggregate_failures, type: :component do
+  let_it_be(:user, refind: true) { create(:user) }
+  let_it_be(:namespace) { user.namespace }
+  let_it_be(:content_class) { '_content_class_' }
+
+  let(:preview_free_user_cap_over?) { true }
+  let(:title) { 'From June 22, 2022 (GitLab 15.1), you can have a maximum' }
+
+  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
+
+  before do
+    allow_next_instance_of(::Namespaces::FreeUserCap::Preview) do |preview_free_user_cap|
+      allow(preview_free_user_cap).to receive(:over_limit?).and_return(preview_free_user_cap_over?)
+    end
+  end
+
+  context 'when over limit' do
+    it 'has content for the preview alert' do
+      render_inline(component)
+
+      expect(rendered_component).to have_selector(".#{content_class}")
+      expect(rendered_component).to have_content(title)
+      expect(rendered_component).to have_link('View all personal projects', href: user_projects_path(user.username))
+      expect(rendered_component).to have_link('status of Over limit', href: described_class::BLOG_URL)
+      expect(rendered_component).to have_link('move your project to a group')
+      expect(rendered_component)
+        .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
+                         "[data-dismiss-endpoint='#{callouts_path}']" \
+                         "[data-feature-id='#{described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT}']")
+      expect(rendered_component).not_to have_css('[data-testid="user-over-limit-secondary-cta"]')
+    end
+
+    it 'renders all the expected tracking items' do
+      render_inline(component)
+
+      expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+      expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
+                                                 '[data-track-action="dismiss_banner"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+      expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="view_personal_projects"]')
+    end
+
+    context 'when alert has been dismissed' do
+      context 'with a fresh dismissal' do
+        before do
+          create(:callout,
+                 user: user,
+                 feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
+                 dismissed_at: Time.now)
+        end
+
+        it 'does not render the alert' do
+          render_inline(component)
+
+          expect(rendered_component).not_to have_content(title)
+        end
+      end
+
+      context 'when alert dismissal has aged out' do
+        before do
+          create(:callout,
+                 user: user,
+                 feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
+                 dismissed_at: described_class::IGNORE_DISMISSAL_EARLIER_THAN - 1.day)
+        end
+
+        it 'renders the alert' do
+          render_inline(component)
+
+          expect(rendered_component).to have_content(title)
+        end
+      end
+    end
+  end
+
+  context 'when not over the limit' do
+    let(:preview_free_user_cap_over?) { false }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user is not authorized to see alert' do
+    let_it_be(:user) { create(:user) }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user does not exist' do
+    let_it_be(:user) { nil }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+end
diff --git a/ee/spec/components/namespaces/free_user_cap/preview_alert_component_spec.rb b/ee/spec/components/namespaces/free_user_cap/preview_alert_component_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..15266622f2ab559f532c2e09e39baed44f6160f0
--- /dev/null
+++ b/ee/spec/components/namespaces/free_user_cap/preview_alert_component_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+RSpec.describe Namespaces::FreeUserCap::PreviewAlertComponent, :saas, :aggregate_failures, type: :component do
+  let_it_be(:namespace) { create(:group) }
+  let_it_be(:user, refind: true) { create(:user) }
+  let_it_be(:content_class) { '_content_class_' }
+
+  let(:preview_free_user_cap_over?) { true }
+  let(:title) { 'From June 22, 2022 (GitLab 15.1), free personal namespaces' }
+
+  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
+
+  before do
+    allow_next_instance_of(::Namespaces::FreeUserCap::Preview) do |preview_free_user_cap|
+      allow(preview_free_user_cap).to receive(:over_limit?).and_return(preview_free_user_cap_over?)
+    end
+  end
+
+  context 'when user is authorized to see alert' do
+    before do
+      namespace.add_owner(user)
+    end
+
+    context 'when over limit' do
+      it 'has content for the preview alert' do
+        render_inline(component)
+
+        expect(rendered_component).to have_selector(".#{content_class}")
+        expect(rendered_component).to have_content(title)
+        expect(rendered_component).to have_link('Manage members', href: group_usage_quotas_path(namespace))
+        expect(rendered_component).to have_link('Explore paid plans', href: group_billings_path(namespace))
+        expect(rendered_component).to have_link('status of Over limit', href: described_class::BLOG_URL)
+        expect(rendered_component)
+          .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
+                         "[data-dismiss-endpoint='#{group_callouts_path}']" \
+                         "[data-feature-id='#{described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT}']" \
+                         "[data-group-id='#{namespace.id}']")
+      end
+
+      it 'renders all the expected tracking items' do
+        render_inline(component)
+
+        expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
+                                                 '[data-track-action="dismiss_banner"]' \
+                                                 '[data-track-label="user_limit_banner"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="manage_members"]')
+        expect(rendered_component).to have_css('[data-testid="user-over-limit-secondary-cta"]' \
+                                                 '[data-track-action="click_button"]' \
+                                                 '[data-track-label="explore_paid_plans"]')
+      end
+
+      context 'when alert has been dismissed' do
+        context 'with a fresh dismissal' do
+          before do
+            create(:group_callout,
+                   user: user,
+                   group: namespace,
+                   feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
+                   dismissed_at: Time.now)
+          end
+
+          it 'does not render the alert' do
+            render_inline(component)
+
+            expect(rendered_component).not_to have_content(title)
+          end
+        end
+
+        context 'when alert dismissal has aged out' do
+          before do
+            create(:group_callout,
+                   user: user,
+                   group: namespace,
+                   feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
+                   dismissed_at: described_class::IGNORE_DISMISSAL_EARLIER_THAN - 1.day)
+          end
+
+          it 'renders the alert' do
+            render_inline(component)
+
+            expect(rendered_component).to have_content(title)
+          end
+        end
+      end
+    end
+
+    context 'when not over the limit' do
+      let(:preview_free_user_cap_over?) { false }
+
+      it 'does not render the alert' do
+        render_inline(component)
+
+        expect(rendered_component).not_to have_content(title)
+      end
+    end
+  end
+
+  context 'when user is not authorized to see alert' do
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+
+  context 'when user does not exist' do
+    let_it_be(:user) { nil }
+
+    it 'does not render the alert' do
+      render_inline(component)
+
+      expect(rendered_component).not_to have_content(title)
+    end
+  end
+end
diff --git a/ee/spec/components/namespaces/free_user_cap_alert_component_spec.rb b/ee/spec/components/namespaces/free_user_cap_alert_component_spec.rb
deleted file mode 100644
index 96a165254bfb256551733e2b535447357de4698d..0000000000000000000000000000000000000000
--- a/ee/spec/components/namespaces/free_user_cap_alert_component_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe Namespaces::FreeUserCapAlertComponent, :saas, :aggregate_failures, type: :component do
-  let_it_be(:namespace, reload: true) { create(:group) }
-  let_it_be(:user, refind: true) { create(:user) }
-  let_it_be(:content_class) { '_content_class_' }
-
-  let(:free_user_cap_reached_limit?) { true }
-
-  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
-
-  context 'when namespace is a group' do
-    let(:title) { "Looks like you've reached your #{::Namespaces::FreeUserCap::FREE_USER_LIMIT} member limit" }
-
-    context 'when user is authorized to see alert' do
-      before do
-        namespace.add_owner(user)
-
-        allow_next_instance_of(::Namespaces::FreeUserCap) do |free_user_cap|
-          allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
-        end
-      end
-
-      context 'when limit has been reached' do
-        it 'has content for the alert' do
-          render_inline(component)
-
-          expect(rendered_component).to have_selector(".#{content_class}")
-          expect(rendered_component).to have_content(title)
-          expect(rendered_component).to have_link('Manage members', href: group_usage_quotas_path(namespace))
-          expect(rendered_component).to have_link('Explore paid plans', href: group_billings_path(namespace))
-          expect(rendered_component)
-            .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
-                         "[data-dismiss-endpoint='#{group_callouts_path}']" \
-                         "[data-feature-id='#{described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT}']" \
-                         "[data-group-id='#{namespace.id}']")
-        end
-
-        it 'renders all the expected tracking items' do
-          render_inline(component)
-
-          expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
-                                                 '[data-track-action="dismiss_banner"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="manage_members"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-secondary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="explore_paid_plans"]')
-        end
-
-        context 'when alert has been dismissed' do
-          context 'with a fresh dismissal' do
-            before do
-              create(:group_callout,
-                     user: user,
-                     group: namespace,
-                     feature_name: described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT)
-            end
-
-            it 'does not render the alert' do
-              render_inline(component)
-
-              expect(rendered_component).not_to have_content(title)
-            end
-          end
-        end
-      end
-
-      context 'when limit has not been reached' do
-        let(:free_user_cap_reached_limit?) { false }
-
-        it 'does not render the alert' do
-          render_inline(component)
-
-          expect(rendered_component).not_to have_content(title)
-        end
-      end
-    end
-  end
-
-  context 'when namespace is a user namespace' do
-    let(:namespace) { user.namespace }
-    let(:title) { "You've reached your #{::Namespaces::FreeUserCap::FREE_USER_LIMIT} member limit" }
-
-    before do
-      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
-        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
-      end
-    end
-
-    context 'when limit has been reached' do
-      it 'has content for the alert' do
-        render_inline(component)
-
-        expect(rendered_component).to have_selector(".#{content_class}")
-        expect(rendered_component).to have_content(title)
-        expect(rendered_component).to have_link('View all personal projects', href: user_projects_path(user.username))
-        expect(rendered_component).to have_link('move your projects to a group')
-        expect(rendered_component)
-          .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
-                         "[data-dismiss-endpoint='#{callouts_path}']" \
-                         "[data-feature-id='#{described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT}']")
-        expect(rendered_component).not_to have_css('[data-testid="user-over-limit-secondary-cta"]')
-      end
-
-      it 'renders all the expected tracking items' do
-        render_inline(component)
-
-        expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-        expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
-                                                 '[data-track-action="dismiss_banner"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-        expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="view_personal_projects"]')
-      end
-
-      context 'when alert has been dismissed' do
-        context 'with a fresh dismissal' do
-          before do
-            create(:callout, user: user, feature_name: described_class::USER_REACHED_LIMIT_FREE_PLAN_ALERT)
-          end
-
-          it 'does not render the alert' do
-            render_inline(component)
-
-            expect(rendered_component).not_to have_content(title)
-          end
-        end
-      end
-    end
-
-    context 'when limit has not been reached' do
-      let(:free_user_cap_reached_limit?) { false }
-
-      it 'does not render the alert' do
-        render_inline(component)
-
-        expect(rendered_component).not_to have_content(title)
-      end
-    end
-  end
-
-  context 'when user is not authorized to see alert' do
-    before do
-      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
-        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
-      end
-    end
-
-    it 'does not render the alert' do
-      render_inline(component)
-
-      expect(rendered_component).not_to have_content("Looks like you've reached")
-    end
-  end
-
-  context 'when user does not exist' do
-    let_it_be(:user) { nil }
-
-    before do
-      allow_next_instance_of(::Namespaces::FreeUserCap::Standard) do |free_user_cap|
-        allow(free_user_cap).to receive(:reached_limit?).and_return(free_user_cap_reached_limit?)
-      end
-    end
-
-    it 'does not render the alert' do
-      render_inline(component)
-
-      expect(rendered_component).not_to have_content("Looks like you've reached")
-    end
-  end
-end
diff --git a/ee/spec/components/namespaces/preview_free_user_cap_alert_component_spec.rb b/ee/spec/components/namespaces/preview_free_user_cap_alert_component_spec.rb
deleted file mode 100644
index 33ead50991b137c6fde357d11228c95e26d0f971..0000000000000000000000000000000000000000
--- a/ee/spec/components/namespaces/preview_free_user_cap_alert_component_spec.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-# frozen_string_literal: true
-require "spec_helper"
-
-RSpec.describe Namespaces::PreviewFreeUserCapAlertComponent, :saas, :aggregate_failures, type: :component do
-  let_it_be(:namespace) { create(:group) }
-  let_it_be(:user, refind: true) { create(:user) }
-  let_it_be(:content_class) { '_content_class_' }
-
-  let(:preview_free_user_cap_over?) { true }
-
-  subject(:component) { described_class.new(namespace: namespace, user: user, content_class: content_class) }
-
-  before do
-    allow_next_instance_of(::Namespaces::PreviewFreeUserCap) do |preview_free_user_cap|
-      allow(preview_free_user_cap).to receive(:over_limit?).and_return(preview_free_user_cap_over?)
-    end
-  end
-
-  context 'when namespace is a group' do
-    let(:title) { 'From June 22, 2022 (GitLab 15.1), free personal namespaces' }
-
-    context 'when user is authorized to see alert' do
-      before do
-        namespace.add_owner(user)
-      end
-
-      context 'when over limit' do
-        it 'has content for the preview alert' do
-          render_inline(component)
-
-          expect(rendered_component).to have_selector(".#{content_class}")
-          expect(rendered_component).to have_content(title)
-          expect(rendered_component).to have_link('Manage members', href: group_usage_quotas_path(namespace))
-          expect(rendered_component).to have_link('Explore paid plans', href: group_billings_path(namespace))
-          expect(rendered_component).to have_link('status of Over limit', href: described_class::BLOG_URL)
-          expect(rendered_component)
-            .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
-                         "[data-dismiss-endpoint='#{group_callouts_path}']" \
-                         "[data-feature-id='#{described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT}']" \
-                         "[data-group-id='#{namespace.id}']")
-        end
-
-        it 'renders all the expected tracking items' do
-          render_inline(component)
-
-          expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
-                                                 '[data-track-action="dismiss_banner"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="manage_members"]')
-          expect(rendered_component).to have_css('[data-testid="user-over-limit-secondary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="explore_paid_plans"]')
-        end
-
-        context 'when alert has been dismissed' do
-          context 'with a fresh dismissal' do
-            before do
-              create(:group_callout,
-                     user: user,
-                     group: namespace,
-                     feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
-                     dismissed_at: Time.now)
-            end
-
-            it 'does not render the alert' do
-              render_inline(component)
-
-              expect(rendered_component).not_to have_content(title)
-            end
-          end
-
-          context 'when alert dismissal has aged out' do
-            before do
-              create(:group_callout,
-                     user: user,
-                     group: namespace,
-                     feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
-                     dismissed_at: described_class::IGNORE_DISMISSAL_EARLIER_THAN - 1.day)
-            end
-
-            it 'renders the alert' do
-              render_inline(component)
-
-              expect(rendered_component).to have_content(title)
-            end
-          end
-        end
-      end
-
-      context 'when not over the limit' do
-        let(:preview_free_user_cap_over?) { false }
-
-        it 'does not render the alert' do
-          render_inline(component)
-
-          expect(rendered_component).not_to have_content(title)
-        end
-      end
-    end
-  end
-
-  context 'when namespace is a user namespace' do
-    let(:namespace) { user.namespace }
-    let(:title) { 'From June 22, 2022 (GitLab 15.1), you can have a maximum' }
-
-    context 'when over limit' do
-      it 'has content for the preview alert' do
-        render_inline(component)
-
-        expect(rendered_component).to have_selector(".#{content_class}")
-        expect(rendered_component).to have_content(title)
-        expect(rendered_component).to have_link('View all personal projects', href: user_projects_path(user.username))
-        expect(rendered_component).to have_link('status of Over limit', href: described_class::BLOG_URL)
-        expect(rendered_component).to have_link('move your project to a group')
-        expect(rendered_component)
-          .to have_css("[data-testid='user-over-limit-free-plan-alert']" \
-                         "[data-dismiss-endpoint='#{callouts_path}']" \
-                         "[data-feature-id='#{described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT}']")
-        expect(rendered_component).not_to have_css('[data-testid="user-over-limit-secondary-cta"]')
-      end
-
-      it 'renders all the expected tracking items' do
-        render_inline(component)
-
-        expect(rendered_component).to have_css('.js-user-over-limit-free-plan-alert[data-track-action="render"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-        expect(rendered_component).to have_css('[data-testid="user-over-limit-free-plan-dismiss"]' \
-                                                 '[data-track-action="dismiss_banner"]' \
-                                                 '[data-track-label="user_limit_banner"]')
-        expect(rendered_component).to have_css('[data-testid="user-over-limit-primary-cta"]' \
-                                                 '[data-track-action="click_button"]' \
-                                                 '[data-track-label="view_personal_projects"]')
-      end
-
-      context 'when alert has been dismissed' do
-        context 'with a fresh dismissal' do
-          before do
-            create(:callout,
-                   user: user,
-                   feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
-                   dismissed_at: Time.now)
-          end
-
-          it 'does not render the alert' do
-            render_inline(component)
-
-            expect(rendered_component).not_to have_content(title)
-          end
-        end
-
-        context 'when alert dismissal has aged out' do
-          before do
-            create(:callout,
-                   user: user,
-                   feature_name: described_class::PREVIEW_USER_OVER_LIMIT_FREE_PLAN_ALERT,
-                   dismissed_at: described_class::IGNORE_DISMISSAL_EARLIER_THAN - 1.day)
-          end
-
-          it 'renders the alert' do
-            render_inline(component)
-
-            expect(rendered_component).to have_content(title)
-          end
-        end
-      end
-    end
-
-    context 'when not over the limit' do
-      let(:preview_free_user_cap_over?) { false }
-
-      it 'does not render the alert' do
-        render_inline(component)
-
-        expect(rendered_component).not_to have_content(title)
-      end
-    end
-  end
-
-  context 'when user is not authorized to see alert' do
-    it 'does not render the alert' do
-      render_inline(component)
-
-      expect(rendered_component).not_to have_content('From June 22, 2022 (GitLab 15.1)')
-    end
-  end
-
-  context 'when user does not exist' do
-    let_it_be(:user) { nil }
-
-    it 'does not render the alert' do
-      render_inline(component)
-
-      expect(rendered_component).not_to have_content('From June 22, 2022 (GitLab 15.1)')
-    end
-  end
-end
diff --git a/ee/spec/views/groups/group_members/index.html.haml_spec.rb b/ee/spec/views/groups/group_members/index.html.haml_spec.rb
index 57e23b4e9fc75d83f864421ad28218c9e22949f1..532db57f017c6e995faa9b72f1f1fcc4d2c5da3b 100644
--- a/ee/spec/views/groups/group_members/index.html.haml_spec.rb
+++ b/ee/spec/views/groups/group_members/index.html.haml_spec.rb
@@ -16,7 +16,7 @@
     it 'renders the alert partial' do
       render
 
-      expect(rendered).to render_template('shared/_user_over_limit_free_plan_alert')
+      expect(rendered).to render_template('shared/_free_user_cap_alert')
     end
   end
 end
diff --git a/ee/spec/views/groups/show.html.haml_spec.rb b/ee/spec/views/groups/show.html.haml_spec.rb
index 056e1968bdd08e9ec74df7e86c72817bf1872f53..bbbe832c2f90bd7e61c94f7d8cc590e05e03a9eb 100644
--- a/ee/spec/views/groups/show.html.haml_spec.rb
+++ b/ee/spec/views/groups/show.html.haml_spec.rb
@@ -18,7 +18,7 @@
     it 'renders the alert partial' do
       render
 
-      expect(rendered).to render_template('shared/_user_over_limit_free_plan_alert')
+      expect(rendered).to render_template('shared/_free_user_cap_alert')
     end
   end
 end
diff --git a/ee/spec/views/projects/empty.html.haml_spec.rb b/ee/spec/views/projects/empty.html.haml_spec.rb
index 2afe07bb12b6587eac827e50987a0f4959a23aa7..c63bf548d1448a22c83d1c7f254ba6a39c1185bd 100644
--- a/ee/spec/views/projects/empty.html.haml_spec.rb
+++ b/ee/spec/views/projects/empty.html.haml_spec.rb
@@ -17,7 +17,7 @@
     it 'renders the alert partial' do
       render
 
-      expect(rendered).to render_template('shared/_user_over_limit_free_plan_alert')
+      expect(rendered).to render_template('projects/_free_user_cap_alert')
     end
   end
 end
diff --git a/ee/spec/views/projects/project_members/index.html.haml_spec.rb b/ee/spec/views/projects/project_members/index.html.haml_spec.rb
index a5af60d6b9dabac497af671e71d1628724e42ce4..283872bcbaa110c4712bccbc2a7a4bb1aeaefe5b 100644
--- a/ee/spec/views/projects/project_members/index.html.haml_spec.rb
+++ b/ee/spec/views/projects/project_members/index.html.haml_spec.rb
@@ -74,7 +74,7 @@
     it 'renders the alert partial' do
       render
 
-      expect(rendered).to render_template('shared/_user_over_limit_free_plan_alert')
+      expect(rendered).to render_template('projects/_free_user_cap_alert')
     end
   end
 end
diff --git a/ee/spec/views/projects/show.html.haml_spec.rb b/ee/spec/views/projects/show.html.haml_spec.rb
index c9245f20f1395e08f84fb0d3e269518f3d475bd1..370e0dd55c6aa7feb8978a62437ef3d485575c27 100644
--- a/ee/spec/views/projects/show.html.haml_spec.rb
+++ b/ee/spec/views/projects/show.html.haml_spec.rb
@@ -18,7 +18,7 @@
     it 'renders the alert partial' do
       render
 
-      expect(rendered).to render_template('shared/_user_over_limit_free_plan_alert')
+      expect(rendered).to render_template('projects/_free_user_cap_alert')
     end
   end
 end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 433fd1566c9f4192fb079528a8eb937cc697ef10..505b92fdd5d7a7e008918e8670f801ca78403df8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -43726,7 +43726,7 @@ msgstr ""
 msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
 msgstr ""
 
-msgid "You've reached your %{free_limit} member limit across all your personal projects"
+msgid "You've reached your %{free_limit} member limit across all of your personal projects"
 msgstr ""
 
 msgid "You've rejected %{user}"