diff --git a/app/assets/javascripts/discourse/app/components/route-control.hbs b/app/assets/javascripts/discourse/app/components/route-control.hbs
new file mode 100644
index 0000000000..83d2bf0af9
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/route-control.hbs
@@ -0,0 +1,3 @@
+{{#if this.canDisplay}}
+ {{yield}}
+{{/if}}
diff --git a/app/assets/javascripts/discourse/app/components/route-control.js b/app/assets/javascripts/discourse/app/components/route-control.js
new file mode 100644
index 0000000000..e1eee5b605
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/route-control.js
@@ -0,0 +1,51 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import { defaultHomepage } from "discourse/lib/utilities";
+
+export default class RouteControl extends Component {
+ @service router;
+ @service siteSettings;
+ @service currentUser;
+
+ get canDisplay() {
+ const currentRouteName = this.router.currentRouteName;
+ const showOn = this.args.showOn;
+ const minTrustLevel = this.args.options?.minTrustLevel;
+ const requireUser = this.args.options?.requireUser
+ ? this.args.options?.requireUser
+ : false;
+
+ if (requireUser && !this.currentUser) {
+ return;
+ }
+
+ if (
+ (minTrustLevel && this.currentUser?.trust_level < minTrustLevel) ||
+ (minTrustLevel && !this.currentUser)
+ ) {
+ return;
+ }
+
+ if (showOn === "homepage") {
+ return this.#handleHomepageRoute(currentRouteName);
+ } else if (showOn === currentRouteName) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ #handleHomepageRoute(currentRouteName) {
+ const topMenu = this.siteSettings.top_menu;
+
+ if (currentRouteName === `discovery.${defaultHomepage()}`) {
+ return true;
+ } else if (
+ topMenu.split("|").any((m) => `discovery.${m}` === currentRouteName)
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/app/assets/javascripts/discourse/tests/acceptance/route-control-test.js b/app/assets/javascripts/discourse/tests/acceptance/route-control-test.js
new file mode 100644
index 0000000000..d2d7d72c9a
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/acceptance/route-control-test.js
@@ -0,0 +1,117 @@
+import {
+ acceptance,
+ exists,
+ updateCurrentUser,
+} from "discourse/tests/helpers/qunit-helpers";
+import { visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import { extraConnectorClass } from "discourse/lib/plugin-connectors";
+import { hbs } from "ember-cli-htmlbars";
+
+const PREFIX = "javascripts/single-test/connectors";
+
+acceptance("Route Control (as visitor)", function (needs) {
+ needs.settings({
+ top_menu: "latest|categories|top|bookmarks",
+ });
+ needs.hooks.beforeEach(() => {
+ extraConnectorClass("below-site-header/hello");
+
+ // eslint-disable-next-line no-undef
+ Ember.TEMPLATES[`${PREFIX}/below-site-header/hello`] = hbs`
+
+ This should only show on the category page
+
+
+ This should only show on the homepage
+
+
+ Only users can see this
+ `;
+ });
+
+ test("Category content only shows on category page", async function (assert) {
+ await visit("/categories");
+ assert.ok(
+ exists(
+ "#only-category-page",
+ "The content is shown on the category page."
+ )
+ );
+ await visit("/latest");
+ assert.ok(
+ !exists(
+ "#only-category-page",
+ "The content is not shown on the latest page."
+ )
+ );
+ });
+
+ test("Homepage Content only shows on all homepage routes", async function (assert) {
+ await visit("/");
+ assert.ok(
+ exists("#only-homepage", "The content is shown on the homepage.")
+ );
+
+ await visit("/categories");
+ assert.ok(
+ exists("#only-homepage", "The content is shown on a homepage route.")
+ );
+
+ await visit("/t/internationalization-localization/280");
+ assert.ok(
+ !exists(
+ "#only-homepage",
+ "The content is not shown on the categories page."
+ )
+ );
+ });
+
+ test("User only content doesn't show for visitors", async function (assert) {
+ await visit("/latest");
+ assert.ok(!exists("#only-users"), "The content is not shown for visitors.");
+ });
+});
+
+acceptance("Route Control (as user)", function (needs) {
+ needs.user();
+ needs.settings({
+ top_menu: "latest|categories|top|bookmarks",
+ });
+ needs.hooks.beforeEach(() => {
+ extraConnectorClass("below-site-header/hello");
+
+ // eslint-disable-next-line no-undef
+ Ember.TEMPLATES[`${PREFIX}/below-site-header/hello`] = hbs`
+
+ Only users can see this
+
+
+ This should only show for 3 or above trust level
+
+`;
+ });
+
+ test("User only content shows for users", async function (assert) {
+ await visit("/latest");
+ assert.ok(exists("#only-users"), "The content is visible for users.");
+ });
+
+ test("Content shows for trust level 3 user", async function (assert) {
+ await visit("/");
+ assert.ok(
+ exists("#only-trust-level"),
+ "The content is visible for adequate trust level user."
+ );
+ });
+
+ test("Content doesn't show for trust level 2 user", async function (assert) {
+ updateCurrentUser({ trust_level: 2 });
+ await visit("/");
+
+ assert.ok(
+ !exists("#only-trust-level"),
+ "The content is not shown for low trust level user."
+ );
+ });
+});