diff --git a/assets/js/components/settings/constants.js b/assets/js/components/settings/constants.js
new file mode 100644
index 00000000000..60a60c78933
--- /dev/null
+++ b/assets/js/components/settings/constants.js
@@ -0,0 +1,19 @@
+/**
+ * Settings constants.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const NEW_MODULES = [ 'ads', 'reader-revenue-manager' ];
diff --git a/assets/js/googlesitekit-modules-reader-revenue-manager.js b/assets/js/googlesitekit-modules-reader-revenue-manager.js
new file mode 100644
index 00000000000..beff1a09e08
--- /dev/null
+++ b/assets/js/googlesitekit-modules-reader-revenue-manager.js
@@ -0,0 +1,30 @@
+/**
+ * Reader Revenue Manager module entrypoint.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import Data from 'googlesitekit-data';
+import Modules from 'googlesitekit-modules';
+import {
+ registerStore,
+ registerModule,
+} from './modules/reader-revenue-manager';
+
+registerStore( Data );
+registerModule( Modules );
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.js
new file mode 100644
index 00000000000..19e6cf56ad3
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.js
@@ -0,0 +1,37 @@
+/**
+ * Reader Revenue Manager SettingsEdit component.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+export default function SettingsEdit() {
+ return (
+
+
+ { /* TODO: Add the rest of the settings steps */ }
+
+ );
+}
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.stories.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.stories.js
new file mode 100644
index 00000000000..9901e1830c6
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.stories.js
@@ -0,0 +1,34 @@
+/**
+ * Reader Revenue Manager SettingsEdit component stories.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import SettingsEdit from './SettingsEdit';
+
+function Template() {
+ return ;
+}
+
+export const Default = Template.bind( {} );
+Default.storyName = 'Default';
+
+export default {
+ title: 'Modules/ReaderRevenueManager/Settings/SettingsEdit',
+ component: SettingsEdit,
+};
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.test.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.test.js
new file mode 100644
index 00000000000..66bf0edc12c
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsEdit.test.js
@@ -0,0 +1,30 @@
+/**
+ * Reader Revenue Manager SettingsEdit component tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render } from '../../../../../../tests/js/test-utils';
+import SettingsEdit from './SettingsEdit';
+
+describe( 'SettingsEdit', () => {
+ it( 'should render the component', () => {
+ const { getByText } = render( );
+
+ expect(
+ getByText( /Reader Revenue Manager Settings Edit/i )
+ ).toBeInTheDocument();
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.js
new file mode 100644
index 00000000000..dfe95f39571
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.js
@@ -0,0 +1,37 @@
+/**
+ * Reader Revenue Manager SettingsView component.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+export default function SettingsView() {
+ return (
+
+
+ { /* TODO: Add the rest of the settings steps */ }
+
+ );
+}
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.stories.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.stories.js
new file mode 100644
index 00000000000..3f317700f12
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.stories.js
@@ -0,0 +1,34 @@
+/**
+ * Reader Revenue Manager SettingsView component stories.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import SettingsView from './SettingsView';
+
+function Template() {
+ return ;
+}
+
+export const Default = Template.bind( {} );
+Default.storyName = 'Default';
+
+export default {
+ title: 'Modules/ReaderRevenueManager/Settings/SettingsView',
+ component: SettingsView,
+};
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.test.js b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.test.js
new file mode 100644
index 00000000000..96e585ac452
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/SettingsView.test.js
@@ -0,0 +1,30 @@
+/**
+ * Reader Revenue Manager SettingsView component tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render } from '../../../../../../tests/js/test-utils';
+import SettingsView from './SettingsView';
+
+describe( 'SettingsView', () => {
+ it( 'should render the component', () => {
+ const { getByText } = render( );
+
+ expect(
+ getByText( /Reader Revenue Manager Settings View/i )
+ ).toBeInTheDocument();
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/components/settings/index.js b/assets/js/modules/reader-revenue-manager/components/settings/index.js
new file mode 100644
index 00000000000..a5495ca2804
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/settings/index.js
@@ -0,0 +1,20 @@
+/**
+ * Reader Revenue Manager Settings components.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { default as SettingsEdit } from './SettingsEdit';
+export { default as SettingsView } from './SettingsView';
diff --git a/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.js b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.js
new file mode 100644
index 00000000000..97fc654097e
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.js
@@ -0,0 +1,47 @@
+/**
+ * Reader Revenue Manager SetupMain component.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * WordPress dependencies
+ */
+import { _x } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import ReaderRevenueManagerIcon from '../../../../../svg/graphics/reader-revenue-manager.svg';
+
+export default function SetupMain() {
+ return (
+
+
+ { /* TODO: Add the rest of the setup steps */ }
+
+ );
+}
diff --git a/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.stories.js b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.stories.js
new file mode 100644
index 00000000000..f83618bbdc4
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.stories.js
@@ -0,0 +1,34 @@
+/**
+ * Reader Revenue Manager SetupMain component stories.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import SetupMain from './SetupMain';
+
+function Template() {
+ return ;
+}
+
+export const Default = Template.bind( {} );
+Default.storyName = 'Default';
+
+export default {
+ title: 'Modules/ReaderRevenueManager/Setup/SetupMain',
+ component: SetupMain,
+};
diff --git a/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.test.js b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.test.js
new file mode 100644
index 00000000000..62b8d736342
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/setup/SetupMain.test.js
@@ -0,0 +1,28 @@
+/**
+ * Reader Revenue Manager SetupMain component tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { render } from '../../../../../../tests/js/test-utils';
+import SetupMain from './SetupMain';
+
+describe( 'SetupMain', () => {
+ it( 'should render the component', () => {
+ const { getByText } = render( );
+
+ expect( getByText( /Reader Revenue Manager/i ) ).toBeInTheDocument();
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/components/setup/index.js b/assets/js/modules/reader-revenue-manager/components/setup/index.js
new file mode 100644
index 00000000000..d0c584f8d23
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/components/setup/index.js
@@ -0,0 +1,19 @@
+/**
+ * Reader Revenue Manager Setup components.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { default as SetupMain } from './SetupMain';
diff --git a/assets/js/modules/reader-revenue-manager/datastore/base.js b/assets/js/modules/reader-revenue-manager/datastore/base.js
new file mode 100644
index 00000000000..da7cfb7a972
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/base.js
@@ -0,0 +1,29 @@
+/**
+ * `modules/reader-revenue-manager` base data store.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import Modules from 'googlesitekit-modules';
+import { MODULES_READER_REVENUE_MANAGER } from './constants';
+import { validateCanSubmitChanges } from './settings';
+
+export default Modules.createModuleStore( 'reader-revenue-manager', {
+ storeName: MODULES_READER_REVENUE_MANAGER,
+ validateCanSubmitChanges,
+} );
diff --git a/assets/js/modules/reader-revenue-manager/datastore/base.test.js b/assets/js/modules/reader-revenue-manager/datastore/base.test.js
new file mode 100644
index 00000000000..98413b2567e
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/base.test.js
@@ -0,0 +1,51 @@
+/**
+ * `modules/reader-revenue-manager` base data store tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import {
+ createTestRegistry,
+ provideSiteInfo,
+} from '../../../../../tests/js/utils';
+import { MODULES_READER_REVENUE_MANAGER } from './constants';
+
+describe( 'modules/reader-revenue-manager base data store', () => {
+ let registry;
+ let store;
+
+ beforeEach( () => {
+ jest.resetModules();
+
+ registry = createTestRegistry();
+ provideSiteInfo( registry );
+ } );
+
+ it( 'does not define the admin page', () => {
+ store = require( './base' ).default;
+ registry.registerStore( MODULES_READER_REVENUE_MANAGER, store );
+
+ expect(
+ registry
+ .select( MODULES_READER_REVENUE_MANAGER )
+ .getAdminScreenURL()
+ ).toBe(
+ 'http://example.com/wp-admin/admin.php?page=googlesitekit-dashboard'
+ );
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/datastore/constants.js b/assets/js/modules/reader-revenue-manager/datastore/constants.js
new file mode 100644
index 00000000000..bd47a04d973
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/constants.js
@@ -0,0 +1,21 @@
+/**
+ * `modules/reader-revenue-manager` data store constants.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const MODULES_READER_REVENUE_MANAGER = 'modules/reader-revenue-manager';
+
+export const ERROR_CODE_NON_HTTPS_SITE = 'non_https_site';
diff --git a/assets/js/modules/reader-revenue-manager/datastore/index.js b/assets/js/modules/reader-revenue-manager/datastore/index.js
new file mode 100644
index 00000000000..9ce3ea73875
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/index.js
@@ -0,0 +1,39 @@
+/**
+ * `modules/reader-revenue-manager` data store.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import { combineStores } from 'googlesitekit-data';
+import { MODULES_READER_REVENUE_MANAGER } from './constants';
+import baseModuleStore from './base';
+
+const store = combineStores( baseModuleStore );
+
+export const initialState = store.initialState;
+export const actions = store.actions;
+export const controls = store.controls;
+export const reducer = store.reducer;
+export const resolvers = store.resolvers;
+export const selectors = store.selectors;
+
+export const registerStore = ( registry ) => {
+ registry.registerStore( MODULES_READER_REVENUE_MANAGER, store );
+};
+
+export default store;
diff --git a/assets/js/modules/reader-revenue-manager/datastore/index.test.js b/assets/js/modules/reader-revenue-manager/datastore/index.test.js
new file mode 100644
index 00000000000..1d7dfd0f5f9
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/index.test.js
@@ -0,0 +1,43 @@
+/**
+ * `modules/reader-revenue-manager` data store: selectors test.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import {
+ createTestRegistry,
+ unsubscribeFromAll,
+} from '../../../../../tests/js/utils';
+
+describe( 'modules/reader-revenue-manager properties', () => {
+ let registry;
+
+ beforeEach( () => {
+ registry = createTestRegistry();
+ } );
+
+ afterEach( () => {
+ unsubscribeFromAll( registry );
+ } );
+
+ describe( 'store', () => {
+ it( 'is registered correctly', () => {
+ // TODO: Implement tests as part of #8793.
+ } );
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/datastore/settings.js b/assets/js/modules/reader-revenue-manager/datastore/settings.js
new file mode 100644
index 00000000000..57c408bef23
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/settings.js
@@ -0,0 +1,55 @@
+/**
+ * `modules/reader-revenue-manager` data store: settings actions.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies
+ */
+import invariant from 'invariant';
+
+/**
+ * Internal dependencies
+ */
+import { MODULES_READER_REVENUE_MANAGER } from './constants';
+import {
+ INVARIANT_DOING_SUBMIT_CHANGES,
+ INVARIANT_SETTINGS_NOT_CHANGED,
+} from '../../../googlesitekit/data/create-settings-store';
+import { createStrictSelect } from '../../../googlesitekit/data/utils';
+import { isValidPublicationID } from '../utils/validation';
+
+// Invariant error messages.
+export const INVARIANT_INVALID_PUBLICATION_ID =
+ 'a valid publicationID is required';
+
+export function validateCanSubmitChanges( select ) {
+ const strictSelect = createStrictSelect( select );
+ // Strict select will cause all selector functions to throw an error
+ // if `undefined` is returned, otherwise it behaves the same as `select`.
+ // This ensures that the selector returns `false` until all data dependencies are resolved.
+ const { haveSettingsChanged, isDoingSubmitChanges, getPublicationID } =
+ strictSelect( MODULES_READER_REVENUE_MANAGER );
+
+ invariant( ! isDoingSubmitChanges(), INVARIANT_DOING_SUBMIT_CHANGES );
+ invariant( haveSettingsChanged(), INVARIANT_SETTINGS_NOT_CHANGED );
+
+ const publicationID = getPublicationID();
+ invariant(
+ isValidPublicationID( publicationID ),
+ INVARIANT_INVALID_PUBLICATION_ID
+ );
+}
diff --git a/assets/js/modules/reader-revenue-manager/datastore/settings.test.js b/assets/js/modules/reader-revenue-manager/datastore/settings.test.js
new file mode 100644
index 00000000000..f772e88a2bb
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/datastore/settings.test.js
@@ -0,0 +1,52 @@
+/**
+ * `modules/reader-revenue-manager` data store: settings tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import API from 'googlesitekit-api';
+import {
+ createTestRegistry,
+ unsubscribeFromAll,
+} from '../../../../../tests/js/utils';
+
+describe( 'modules/reader-revenue-manager settings', () => {
+ let registry;
+
+ beforeAll( () => {
+ API.setUsingCache( false );
+ } );
+
+ beforeEach( () => {
+ registry = createTestRegistry();
+ } );
+
+ afterEach( () => {
+ unsubscribeFromAll( registry );
+ } );
+
+ afterAll( () => {
+ API.setUsingCache( true );
+ } );
+
+ describe( 'validateCanSubmitChanges', () => {
+ it( 'it validates', () => {
+ // TODO: Implement tests as part of #8793.
+ } );
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/index.js b/assets/js/modules/reader-revenue-manager/index.js
new file mode 100644
index 00000000000..8f95b53edac
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/index.js
@@ -0,0 +1,79 @@
+/**
+ * Reader Revenue Manager module initialization.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
+import {
+ MODULES_READER_REVENUE_MANAGER,
+ ERROR_CODE_NON_HTTPS_SITE,
+} from './datastore/constants';
+import { SetupMain } from './components/setup';
+import { SettingsEdit, SettingsView } from './components/settings';
+import ReaderRevenueManagerIcon from '../../../svg/graphics/reader-revenue-manager.svg';
+import { isFeatureEnabled } from '../../features';
+import { isURLUsingHTTPS } from './utils/validation';
+
+export { registerStore } from './datastore';
+
+const isRrmModuleEnabled =
+ ( func ) =>
+ ( ...args ) => {
+ if ( isFeatureEnabled( 'rrmModule' ) ) {
+ func( ...args );
+ }
+ };
+
+export const registerModule = isRrmModuleEnabled( ( modules ) => {
+ modules.registerModule( 'reader-revenue-manager', {
+ storeName: MODULES_READER_REVENUE_MANAGER,
+ SettingsEditComponent: SettingsEdit,
+ SettingsViewComponent: SettingsView,
+ SetupComponent: SetupMain,
+ Icon: ReaderRevenueManagerIcon,
+ features: [
+ // TODO: Implement the features as part of #8845.
+ ],
+ checkRequirements: async ( registry ) => {
+ // Ensure the site info is resolved to get the home URL.
+ await registry
+ .__experimentalResolveSelect( CORE_SITE )
+ .getSiteInfo();
+ const homeURL = registry.select( CORE_SITE ).getHomeURL();
+
+ if ( isURLUsingHTTPS( homeURL ) ) {
+ return;
+ }
+
+ throw {
+ code: ERROR_CODE_NON_HTTPS_SITE,
+ message: __(
+ 'The site should use HTTPS to set up Reader Revenue Manager',
+ 'google-site-kit'
+ ),
+ data: null,
+ };
+ },
+ } );
+} );
diff --git a/assets/js/modules/reader-revenue-manager/utils/validation.js b/assets/js/modules/reader-revenue-manager/utils/validation.js
new file mode 100644
index 00000000000..507a2693c06
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/utils/validation.js
@@ -0,0 +1,54 @@
+/**
+ * Validation utilities.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Checks if the given publication ID appears to be a valid.
+ *
+ * @since n.e.x.t
+ *
+ * @param {string} publicationID Publication ID to test.
+ * @return {boolean} `true` if the given publication ID is valid, `false` otherwise.
+ */
+export function isValidPublicationID( publicationID ) {
+ return (
+ typeof publicationID === 'string' &&
+ /^[A-Za-z0-9_-]+$/.test( publicationID )
+ );
+}
+
+/**
+ * Checks if a given URL uses HTTPS.
+ *
+ * @since n.e.x.t
+ *
+ * @param {string} url The URL to check.
+ * @return {boolean} True if the URL uses HTTPS, false otherwise.
+ */
+export const isURLUsingHTTPS = ( url ) => {
+ try {
+ if ( typeof url !== 'string' || ! url ) {
+ throw new TypeError( `Invalid URL: ${ url }` );
+ }
+
+ const parsedURL = new URL( url );
+ return parsedURL.protocol === 'https:';
+ } catch ( error ) {
+ global.console.warn( 'Invalid URL:', error );
+ return false;
+ }
+};
diff --git a/assets/js/modules/reader-revenue-manager/utils/validation.test.js b/assets/js/modules/reader-revenue-manager/utils/validation.test.js
new file mode 100644
index 00000000000..b2000b896a8
--- /dev/null
+++ b/assets/js/modules/reader-revenue-manager/utils/validation.test.js
@@ -0,0 +1,69 @@
+/**
+ * Validation function tests.
+ *
+ * Site Kit by Google, Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Internal dependencies
+ */
+import { isValidPublicationID, isURLUsingHTTPS } from './validation';
+
+describe( 'utility functions', () => {
+ describe( 'isValidPublicationID', () => {
+ it( 'should return TRUE when a valid publication ID is passed', () => {
+ expect( isValidPublicationID( 'valid_publication-123' ) ).toBe(
+ true
+ );
+ } );
+
+ it.each( [
+ [ 'false', false ],
+ [ 'an integer', 12345 ],
+ [ 'an empty string', '' ],
+ [ 'a string with invalid characters', 'invalid.publication!ID' ],
+ [ 'a string with periods', 'invalid.publication.ID' ],
+ ] )( 'should return FALSE when %s is passed', ( _, publicationID ) => {
+ expect( isValidPublicationID( publicationID ) ).toBe( false );
+ } );
+ } );
+
+ describe( 'isURLUsingHTTPS', () => {
+ it( 'should return TRUE when a URL with HTTPS is passed', () => {
+ expect( isURLUsingHTTPS( 'https://example.com' ) ).toBe( true );
+ expect( console ).not.toHaveWarned();
+ } );
+
+ it.each( [
+ [ 'a string without protocol', 'example.com' ],
+ [ 'an empty string', '' ],
+ [ 'false', false ],
+ ] )( 'should return FALSE and warn when %s is passed', ( _, url ) => {
+ expect( isURLUsingHTTPS( url ) ).toBe( false );
+ expect( console ).toHaveWarned();
+ } );
+
+ it.each( [
+ [ 'an HTTP URL', 'http://example.com' ],
+ [ 'an invalid URL', 'htp://example.com' ],
+ ] )(
+ 'should return FALSE but not warn when %s is passed',
+ ( _, url ) => {
+ expect( isURLUsingHTTPS( url ) ).toBe( false );
+ expect( console ).not.toHaveWarned();
+ }
+ );
+ } );
+} );
diff --git a/assets/svg/graphics/reader-revenue-manager.svg b/assets/svg/graphics/reader-revenue-manager.svg
new file mode 100644
index 00000000000..8860e5d866d
--- /dev/null
+++ b/assets/svg/graphics/reader-revenue-manager.svg
@@ -0,0 +1,23 @@
+
diff --git a/includes/Modules/Reader_Revenue_Manager.php b/includes/Modules/Reader_Revenue_Manager.php
index bd49d782cef..c19d98202f5 100644
--- a/includes/Modules/Reader_Revenue_Manager.php
+++ b/includes/Modules/Reader_Revenue_Manager.php
@@ -10,7 +10,10 @@
namespace Google\Site_Kit\Modules;
+use Google\Site_Kit\Core\Assets\Script;
use Google\Site_Kit\Core\Modules\Module;
+use Google\Site_Kit\Core\Modules\Module_With_Assets;
+use Google\Site_Kit\Core\Modules\Module_With_Assets_Trait;
use Google\Site_Kit\Core\Modules\Module_With_Scopes;
use Google\Site_Kit\Core\Modules\Module_With_Scopes_Trait;
@@ -21,7 +24,8 @@
* @access private
* @ignore
*/
-final class Reader_Revenue_Manager extends Module implements Module_With_Scopes {
+final class Reader_Revenue_Manager extends Module implements Module_With_Scopes, Module_With_Assets {
+ use Module_With_Assets_Trait;
use Module_With_Scopes_Trait;
/**
@@ -67,4 +71,33 @@ protected function setup_info() {
'homepage' => __( 'https://readerrevenue.withgoogle.com/', 'google-site-kit' ),
);
}
+
+ /**
+ * Sets up the module's assets to register.
+ *
+ * @since n.e.x.t
+ *
+ * @return Asset[] List of Asset objects.
+ */
+ protected function setup_assets() {
+ $base_url = $this->context->url( 'dist/assets/' );
+
+ return array(
+ new Script(
+ 'googlesitekit-modules-reader-revenue-manager',
+ array(
+ 'src' => $base_url . 'js/googlesitekit-modules-reader-revenue-manager.js',
+ 'dependencies' => array(
+ 'googlesitekit-vendor',
+ 'googlesitekit-api',
+ 'googlesitekit-data',
+ 'googlesitekit-modules',
+ 'googlesitekit-datastore-site',
+ 'googlesitekit-datastore-user',
+ 'googlesitekit-components',
+ ),
+ )
+ ),
+ );
+ }
}
diff --git a/tests/js/utils.js b/tests/js/utils.js
index ad379800e2b..45c9f13343c 100644
--- a/tests/js/utils.js
+++ b/tests/js/utils.js
@@ -42,6 +42,7 @@ import * as modulesAds from '../../assets/js/modules/ads';
import * as modulesAdSense from '../../assets/js/modules/adsense';
import * as modulesAnalytics4 from '../../assets/js/modules/analytics-4';
import * as modulesPageSpeedInsights from '../../assets/js/modules/pagespeed-insights';
+import * as modulesReaderRevenueManager from '../../assets/js/modules/reader-revenue-manager';
import * as modulesSearchConsole from '../../assets/js/modules/search-console';
import * as modulesTagManager from '../../assets/js/modules/tagmanager';
import { CORE_SITE } from '../../assets/js/googlesitekit/datastore/site/constants';
@@ -74,6 +75,7 @@ const allCoreModules = [
modulesAdSense,
modulesAnalytics4,
modulesPageSpeedInsights,
+ modulesReaderRevenueManager,
modulesSearchConsole,
modulesTagManager,
];
diff --git a/webpack/modules.config.js b/webpack/modules.config.js
index d8301f79f48..31d115159e3 100644
--- a/webpack/modules.config.js
+++ b/webpack/modules.config.js
@@ -74,6 +74,8 @@ module.exports = ( mode, rules, ANALYZE ) => {
'./assets/js/googlesitekit-modules-analytics-4.js',
'googlesitekit-modules-pagespeed-insights':
'assets/js/googlesitekit-modules-pagespeed-insights.js',
+ 'googlesitekit-modules-reader-revenue-manager':
+ './assets/js/googlesitekit-modules-reader-revenue-manager.js',
'googlesitekit-modules-search-console':
'./assets/js/googlesitekit-modules-search-console.js',
'googlesitekit-modules-tagmanager':