diff --git a/assets/js/src/settings/index.tsx b/assets/js/src/settings/index.tsx
index 488ba23a35..2b8250701b 100644
--- a/assets/js/src/settings/index.tsx
+++ b/assets/js/src/settings/index.tsx
@@ -1,14 +1,44 @@
import React from 'react';
import ReactDOM from 'react-dom';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { initStore, STORE_NAME } from './store';
-const App = () => (
- <>
-
Settings
- {JSON.stringify((window as any).mailpoet_settings)}
- >
-);
+const App = () => {
+ const isSaving = useSelect(
+ (sel) => sel(STORE_NAME).isSaving(),
+ []
+ );
+ const error = useSelect(
+ (sel) => sel(STORE_NAME).getError(),
+ []
+ );
+ const settings = useSelect(
+ (sel) => sel(STORE_NAME).getSettings(),
+ []
+ );
+ const email = useSelect(
+ (sel) => sel(STORE_NAME).getSetting(['sender', 'address']),
+ []
+ );
+ const actions = useDispatch(STORE_NAME);
+ const setEmail = (event) => {
+ actions.setSetting(['sender', 'address'], event.target.value);
+ };
+ const save = () => {
+ actions.saveSettings(settings);
+ };
+ return (
+ <>
+ Settings
+ {JSON.stringify({ email, isSaving, error })}
+
+
+ >
+ );
+};
const container = document.getElementById('settings_container');
if (container) {
+ initStore((window as any).mailpoet_settings);
ReactDOM.render(, container);
}
diff --git a/assets/js/src/settings/store/actions.ts b/assets/js/src/settings/store/actions.ts
new file mode 100644
index 0000000000..0fe47cf4fe
--- /dev/null
+++ b/assets/js/src/settings/store/actions.ts
@@ -0,0 +1,15 @@
+import { Action, Settings } from './types';
+
+export function setSetting(path: string[], value: any): Action {
+ return { type: 'SET_SETTING', path, value };
+}
+
+export function* saveSettings(data: Settings) {
+ yield { type: 'SAVE_STARTED' };
+ const error = yield { type: 'SEND_DATA_TO_API', data };
+ if (error) {
+ return { type: 'SAVE_FAILED', error };
+ }
+ yield { type: 'TRACK_SETTINGS_SAVED', data };
+ return { type: 'SAVE_DONE' };
+}
diff --git a/assets/js/src/settings/store/controls.ts b/assets/js/src/settings/store/controls.ts
new file mode 100644
index 0000000000..410f171b88
--- /dev/null
+++ b/assets/js/src/settings/store/controls.ts
@@ -0,0 +1,19 @@
+import MailPoet from 'mailpoet';
+
+export async function SEND_DATA_TO_API({ data }) {
+ try {
+ await MailPoet.Ajax.post({
+ api_version: (window as any).mailpoet_api_version,
+ endpoint: 'settings',
+ action: 'set',
+ data,
+ });
+ return false;
+ } catch (res) {
+ return res.errors.map((e) => e.message);
+ }
+}
+
+export function TRACK_SETTINGS_SAVED({ data }) {
+ // ...
+}
diff --git a/assets/js/src/settings/store/create_reducer.ts b/assets/js/src/settings/store/create_reducer.ts
new file mode 100644
index 0000000000..dec9d65051
--- /dev/null
+++ b/assets/js/src/settings/store/create_reducer.ts
@@ -0,0 +1,19 @@
+import _ from 'lodash';
+import { State, Action } from './types';
+
+export default function createReducer(defaultValue: State) {
+ return (state: State = defaultValue, action: Action): State => {
+ switch (action.type) {
+ case 'SET_SETTING':
+ return _.setWith(_.clone(state), ['data', ...action.path], action.value, _.clone);
+ case 'SAVE_STARTED':
+ return { ...state, save: { inProgress: true, error: null } };
+ case 'SAVE_DONE':
+ return { ...state, save: { inProgress: false, error: null } };
+ case 'SAVE_FAILED':
+ return { ...state, save: { inProgress: false, error: action.error } };
+ default:
+ return state;
+ }
+ };
+}
diff --git a/assets/js/src/settings/store/index.ts b/assets/js/src/settings/store/index.ts
new file mode 100644
index 0000000000..03df0849c5
--- /dev/null
+++ b/assets/js/src/settings/store/index.ts
@@ -0,0 +1,17 @@
+import { registerStore } from '@wordpress/data';
+import { Settings } from './types';
+import * as actions from './actions';
+import * as selectors from './selectors';
+import * as controls from './controls';
+import createReducer from './create_reducer';
+import makeDefaultState from './make_default_state';
+
+export const STORE_NAME = 'mailpoet-settings';
+
+export const initStore = (data: Settings) => registerStore(STORE_NAME, {
+ reducer: createReducer(makeDefaultState(data)),
+ actions,
+ selectors,
+ controls,
+ resolvers: {},
+});
diff --git a/assets/js/src/settings/store/make_default_state.ts b/assets/js/src/settings/store/make_default_state.ts
new file mode 100644
index 0000000000..a912017e3a
--- /dev/null
+++ b/assets/js/src/settings/store/make_default_state.ts
@@ -0,0 +1,11 @@
+import { State, Settings } from './types';
+
+export default function makeDefaultState(data: Settings): State {
+ return {
+ save: {
+ inProgress: false,
+ error: null,
+ },
+ data,
+ };
+}
diff --git a/assets/js/src/settings/store/selectors.ts b/assets/js/src/settings/store/selectors.ts
new file mode 100644
index 0000000000..724bf2d270
--- /dev/null
+++ b/assets/js/src/settings/store/selectors.ts
@@ -0,0 +1,22 @@
+import _ from 'lodash';
+import { State, Settings } from './types';
+
+export function getSetting(state: State, path: string[]): any {
+ return _.get(state.data, path);
+}
+
+export function getSettings(state: State): Settings {
+ return state.data;
+}
+
+export function isSaving(state: State): boolean {
+ return state.save.inProgress;
+}
+
+export function hasError(state: State): boolean {
+ return state.save.error !== null;
+}
+
+export function getError(state: State): any {
+ return state.save.error;
+}
diff --git a/assets/js/src/settings/store/types.ts b/assets/js/src/settings/store/types.ts
new file mode 100644
index 0000000000..9ed103eb6a
--- /dev/null
+++ b/assets/js/src/settings/store/types.ts
@@ -0,0 +1,55 @@
+export type Settings = {
+ sender: {
+ name: string
+ address: string
+ }
+ reply_to: {
+ name: string
+ address: string
+ }
+ subscribe: {
+ on_comment: {
+ enabled: boolean
+ label: string
+ segments: string[]
+ }
+ on_register: {
+ enabled: boolean
+ label: string
+ segments: string[]
+ }
+ }
+ subscription: {
+ pages: {
+ manage: string
+ unsubscribe: string
+ confirmation: string
+ captcha: string
+ }
+ segments: string[]
+ }
+ stats_notifications: {
+ enabled: boolean
+ automated: boolean
+ address: string
+ }
+ subscriber_email_notification: {
+ enabled: boolean
+ address: string
+ }
+ // ...
+}
+
+export type State = {
+ data: Settings
+ save: {
+ inProgress: boolean
+ error: any
+ }
+}
+
+export type Action =
+ | { type: 'SET_SETTING'; value: any; path: string[] }
+ | { type: 'SAVE_STARTED' }
+ | { type: 'SAVE_DONE' }
+ | { type: 'SAVE_FAILED'; error: any }
diff --git a/package-lock.json b/package-lock.json
index 656fae229d..d5fdbb6ef3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3968,6 +3968,12 @@
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
"dev": true
},
+ "@types/lodash": {
+ "version": "4.14.149",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
+ "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
+ "dev": true
+ },
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
diff --git a/package.json b/package.json
index 2f580a3336..8de7bdb47f 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.8.3",
"@babel/register": "^7.8.3",
+ "@types/lodash": "^4.14.149",
"@types/react": "^16.9.22",
"@typescript-eslint/eslint-plugin": "^2.21.0",
"@typescript-eslint/parser": "^2.21.0",