diff --git a/.babelrc b/.babelrc
index 385166e9..5bd54790 100644
--- a/.babelrc
+++ b/.babelrc
@@ -4,7 +4,7 @@
"@babel/preset-react"
],
"plugins": [
- "@babel/plugin-proposal-class-properties",
+ "@babel/plugin-transform-class-properties",
"@babel/plugin-transform-runtime"
]
}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 7247ed7e..3013893e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.idea/
node_modules
+dist/
domain-mapping/build/*
/vendor/
diff --git a/.nvmrc b/.nvmrc
index 6e9d5a1e..2edeafb0 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-16.13.1
\ No newline at end of file
+20
\ No newline at end of file
diff --git a/assets/css/DomainMapping.css b/assets/css/DomainMapping.css
new file mode 100644
index 00000000..a91df740
--- /dev/null
+++ b/assets/css/DomainMapping.css
@@ -0,0 +1,44 @@
+/**
+ * Globals.
+ */
+@import "global/breakpoints.css";
+@import "global/colors.css";
+
+/**
+ * Controls.
+ */
+@import "controls/info.css";
+@import "controls/modal.css";
+@import "controls/table.css";
+
+.dmp__domain-management {
+ background-color: var(--bright);
+ margin-top: 1rem;
+
+ & .dmp__domain-management-notices {
+ padding: 1rem 1rem 0;
+ }
+
+ & .dmp__domain-management-toolbar {
+ display: flex;
+ flex-flow: wrap;
+ gap: calc(.5rem);
+ padding: 1rem;
+ width: 100%;
+
+ & .components-button {
+ height: 32px;
+ }
+ }
+
+ & .dmp__domain-table {
+
+ & tr {
+ & td, th {
+ &:first-child {
+ padding-left: 1rem;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/css/controls/info.css b/assets/css/controls/info.css
new file mode 100644
index 00000000..93e71065
--- /dev/null
+++ b/assets/css/controls/info.css
@@ -0,0 +1,5 @@
+.dmp__control-info {
+ display: flex;
+ justify-content: space-between;
+ margin-top: .5rem;
+}
\ No newline at end of file
diff --git a/assets/css/controls/modal.css b/assets/css/controls/modal.css
new file mode 100644
index 00000000..6ca392ed
--- /dev/null
+++ b/assets/css/controls/modal.css
@@ -0,0 +1,14 @@
+.dmp__domain-modal {
+
+ & form {
+ display: flex;
+ flex-direction: column;
+ gap: calc(.5rem);
+ }
+
+ & .dmp__domain-modal-buttons {
+ display: flex;
+ gap: calc(.5rem);
+ justify-content: right;
+ }
+}
\ No newline at end of file
diff --git a/assets/css/controls/table.css b/assets/css/controls/table.css
new file mode 100644
index 00000000..266f57b2
--- /dev/null
+++ b/assets/css/controls/table.css
@@ -0,0 +1,38 @@
+.dmp__domain-table {
+ background-color: var(--bright);
+ border-collapse: collapse;
+ text-align: left;
+ width: 100%;
+
+ & tbody {
+
+ & td {
+ padding: .5rem;
+
+ &.dmp__domain-field-domain {
+ font-weight: bold;
+ }
+
+ & .dmp__domain-table-toggle {
+
+ & .components-toggle-control {
+ margin-bottom: 0;
+ }
+ }
+ }
+ }
+
+ & thead {
+ & th {
+ background-color: var(--gray-two);
+ border-top: 1px solid var(--gray);
+ color: var(--dark);
+ font-weight: normal;
+ padding: .5rem;
+ }
+ }
+
+ & tr {
+ border-top: 1px solid var(--gray);
+ }
+}
\ No newline at end of file
diff --git a/assets/css/global/breakpoints.css b/assets/css/global/breakpoints.css
new file mode 100644
index 00000000..b121b0e8
--- /dev/null
+++ b/assets/css/global/breakpoints.css
@@ -0,0 +1,6 @@
+/**
+ * Used by WordPress Core in the admin.
+ */
+@custom-media --bp-small ( max-width: 600px );
+@custom-media --bp-medium ( min-width: 600px );
+@custom-media --bp-large ( min-width: 1200px );
diff --git a/assets/css/global/colors.css b/assets/css/global/colors.css
new file mode 100644
index 00000000..8d83645a
--- /dev/null
+++ b/assets/css/global/colors.css
@@ -0,0 +1,11 @@
+:root {
+ --primary-color: #044D8C;
+ --secondary-color: #022340;
+
+ --bright: #FFFFFF;
+ --dark: #000000;
+ --gray: #F0F0F0;
+ --gray-two: #FAFAFA;
+ --primary-green: #3DC910;
+ --primary-red: #C9221A;
+}
\ No newline at end of file
diff --git a/assets/js/DomainMapping.js b/assets/js/DomainMapping.js
new file mode 100644
index 00000000..010023f2
--- /dev/null
+++ b/assets/js/DomainMapping.js
@@ -0,0 +1,26 @@
+/**
+ * WordPress dependencies.
+ */
+import { createRoot, render } from '@wordpress/element';
+
+/**
+ * Internal Dependencies
+ */
+import DomainManagement from './components/DomainManagement';
+
+import '../css/DomainMapping.css';
+
+/**
+ * Data Store
+ */
+import '../js/data/domains';
+
+if ( document.body.classList.contains( 'settings_page_domains' ) ) {
+ const rootElement = document.getElementById( 'dmp-root' );
+
+ if ( createRoot ) {
+ createRoot( rootElement ).render( );
+ } else {
+ render( , rootElement );
+ }
+}
diff --git a/assets/js/components/DomainManagement.js b/assets/js/components/DomainManagement.js
new file mode 100644
index 00000000..bbf58f6b
--- /dev/null
+++ b/assets/js/components/DomainManagement.js
@@ -0,0 +1,122 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ Button,
+ Notice,
+ SearchControl,
+} from '@wordpress/components';
+import { Component } from '@wordpress/element';
+import { __, sprintf } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import Table from './domain/Table';
+import NewDomainModal from './domain/modals/NewDomain';
+
+class DomainManagement extends Component {
+ constructor( props ) {
+ super( props );
+
+ this.state = {
+ modalNewDomainOpen: false,
+ notices: [],
+ };
+ }
+
+ render() {
+ return (
+
+ { this.renderNotices() }
+ { this.renderToolbar() }
+
+ { this.renderModals() }
+
+ );
+ }
+
+ renderModals() {
+ const { modalNewDomainOpen } = this.state;
+
+ return (
+ <>
+ { modalNewDomainOpen && (
+ {
+ if ( newDomain ) {
+ this.setState( {
+ ...this.state,
+ modalNewDomainOpen: false,
+ notices: [
+ ...this.state.notices,
+ {
+ id: newDomain.id,
+ message: sprintf(
+ /* translators: %s: domain */
+ __( 'Domain, %s, successfully added.', 'darkmatterplugin' ),
+ newDomain.domain
+ ),
+ status: 'success',
+ },
+ ],
+ } );
+ } else {
+ this.setState( {
+ ...this.state,
+ modalNewDomainOpen: false,
+ } );
+ }
+ } }
+ />
+ ) }
+ >
+ );
+ }
+
+ renderNotices() {
+ const { notices } = this.state;
+
+ return (
+
+ { notices.length > 0 && notices.map( ( { id, message, status } ) => {
+ return {
+ const removeIndex = notices.findIndex( ( item ) => {
+ return id === item.id;
+ } );
+
+ notices.splice( removeIndex, 1 );
+ this.setState( { ...this.state, notices: [ ...notices ] } );
+ } }
+ status={ status }
+ >
+ { message }
+ ;
+ } ) }
+
+ );
+ }
+
+ renderToolbar() {
+ return (
+
+
+
+
+ );
+ }
+}
+
+export default DomainManagement;
diff --git a/assets/js/components/domain/Table.js b/assets/js/components/domain/Table.js
new file mode 100644
index 00000000..b5959e8f
--- /dev/null
+++ b/assets/js/components/domain/Table.js
@@ -0,0 +1,186 @@
+/**
+ * WordPress dependencies
+ */
+import { Button, ToggleControl } from '@wordpress/components';
+import { compose } from '@wordpress/compose';
+import { withDispatch, withSelect } from '@wordpress/data';
+import { Component } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * External dependencies
+ */
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies
+ */
+import { DeleteDomainModal } from './modals/DeleteDomain';
+
+const DOMAIN_TYPE = {
+ 1: __( 'Main', 'darkmatterplugin' ),
+ 2: __( 'Media', 'darkmatterplugin' ),
+};
+
+class Table extends Component {
+ constructor( props ) {
+ super( props );
+
+ this.state = {
+ fields: props.fields ?? [
+ {
+ name: 'domain',
+ label: __( 'Domain', 'darkmatterplugin' ),
+ visible: true,
+ },
+ {
+ name: 'type',
+ label: __( 'Type', 'darkmatterplugin' ),
+ visible: true,
+ },
+ {
+ name: 'is_primary',
+ label: __( 'Primary?', 'darkmatterplugin' ),
+ type: 'toggle',
+ visible: true,
+ },
+ {
+ name: 'is_active',
+ label: __( 'Active?', 'darkmatterplugin' ),
+ type: 'toggle',
+ visible: true,
+ },
+ {
+ name: 'actions',
+ label: __( 'Actions', 'darkmatterplugin' ),
+ visible: true,
+ },
+ ],
+ deleteDomain: null,
+ };
+ }
+
+ render() {
+ const { deleteDomain } = this.state;
+
+ return (
+
+
+ { this.renderHeaders() }
+ { this.renderDomains() }
+
+ { !! deleteDomain && (
+
{
+ this.setState( { deleteDomain: null } );
+ } }
+ />
+ ) }
+
+ );
+ }
+
+ renderDisplay( field, domain ) {
+ if ( 'actions' === field.name ) {
+ return (
+ <>
+