From 6f0defc6a1b1d877782d9db1be55c11e2da83007 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Mon, 4 Jul 2022 17:23:01 +0200 Subject: [PATCH] Added AppContext and basic Form component --- apps/comments-ui/package.json | 1 + apps/comments-ui/src/App.js | 93 ++++++++++++++++++++++-- apps/comments-ui/src/AppContext.js | 15 ++++ apps/comments-ui/src/actions.js | 12 +++ apps/comments-ui/src/components/Form.js | 54 ++++++++++++++ apps/comments-ui/src/index.js | 10 +-- apps/comments-ui/src/utils/check-mode.js | 22 ++++++ apps/comments-ui/src/utils/helpers.js | 16 ++++ apps/comments-ui/yarn.lock | 64 +++++++++++++++- 9 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 apps/comments-ui/src/AppContext.js create mode 100644 apps/comments-ui/src/actions.js create mode 100644 apps/comments-ui/src/components/Form.js create mode 100644 apps/comments-ui/src/utils/check-mode.js create mode 100644 apps/comments-ui/src/utils/helpers.js diff --git a/apps/comments-ui/package.json b/apps/comments-ui/package.json index 944e055439..a396b8d198 100644 --- a/apps/comments-ui/package.json +++ b/apps/comments-ui/package.json @@ -5,6 +5,7 @@ "repository": "git@github.com:TryGhost/comments-ui.git", "author": "Ghost Foundation", "dependencies": { + "@sentry/react": "^7.5.0", "@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.2", "@testing-library/user-event": "14.0.0", diff --git a/apps/comments-ui/src/App.js b/apps/comments-ui/src/App.js index ddc793ba85..2610cce842 100644 --- a/apps/comments-ui/src/App.js +++ b/apps/comments-ui/src/App.js @@ -1,11 +1,94 @@ import './App.css'; +import Form from './components/Form'; +import * as Sentry from '@sentry/react'; +import React from 'react'; +import ActionHandler from './actions'; +import {createPopupNotification} from './utils/helpers'; +import AppContext from './AppContext'; -function App() { +function SentryErrorBoundary({dsn, children}) { + if (dsn) { + return ( + + {children} + + ); + } return ( -
- Hello world -
+ <> + {children} + ); } -export default App; +export default class App extends React.Component { + constructor(props) { + super(props); + + // Todo: this state is work in progress + this.state = { + action: 'init:running', + popupNotification: null, + customSiteUrl: props.customSiteUrl + }; + } + + /** Handle actions from across App and update App state */ + async dispatchAction(action, data) { + clearTimeout(this.timeoutId); + this.setState({ + action: `${action}:running` + }); + try { + const updatedState = await ActionHandler({action, data, state: this.state, api: this.GhostApi}); + this.setState(updatedState); + + /** Reset action state after short timeout if not failed*/ + if (updatedState && updatedState.action && !updatedState.action.includes(':failed')) { + this.timeoutId = setTimeout(() => { + this.setState({ + action: '' + }); + }, 2000); + } + } catch (error) { + const popupNotification = createPopupNotification({ + type: `${action}:failed`, + autoHide: true, closeable: true, status: 'error', state: this.state, + meta: { + error + } + }); + this.setState({ + action: `${action}:failed`, + popupNotification + }); + } + } + + /**Get final App level context from App state*/ + getContextFromState() { + const {action, popupNotification, customSiteUrl} = this.state; + return { + action, + popupNotification, + customSiteUrl, + onAction: (_action, data) => this.dispatchAction(_action, data) + }; + } + + componentWillUnmount() { + /**Clear timeouts and event listeners on unmount */ + clearTimeout(this.timeoutId); + } + + render() { + return ( + + +
+ + + ); + } +} diff --git a/apps/comments-ui/src/AppContext.js b/apps/comments-ui/src/AppContext.js new file mode 100644 index 0000000000..785b5f452d --- /dev/null +++ b/apps/comments-ui/src/AppContext.js @@ -0,0 +1,15 @@ +// Ref: https://reactjs.org/docs/context.html +const React = require('react'); + +const AppContext = React.createContext({ + site: {}, + member: {}, + action: '', + brandColor: '', + pageData: {}, + onAction: (action, data) => { + return {action, data}; + } +}); + +export default AppContext; diff --git a/apps/comments-ui/src/actions.js b/apps/comments-ui/src/actions.js new file mode 100644 index 0000000000..18fb211a9b --- /dev/null +++ b/apps/comments-ui/src/actions.js @@ -0,0 +1,12 @@ +const Actions = { + // Put your actions here +}; + +/** Handle actions in the App, returns updated state */ +export default async function ActionHandler({action, data, state, api}) { + const handler = Actions[action]; + if (handler) { + return await handler({data, state, api}) || {}; + } + return {}; +} diff --git a/apps/comments-ui/src/components/Form.js b/apps/comments-ui/src/components/Form.js new file mode 100644 index 0000000000..a6430f7021 --- /dev/null +++ b/apps/comments-ui/src/components/Form.js @@ -0,0 +1,54 @@ +import React from 'react'; +import AppContext from '../AppContext'; + +class Form extends React.Component { + static contextType = AppContext; + + constructor(props) { + super(props); + this.state = { + message: '' + }; + + this.submitForm = this.submitForm.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + async submitForm(event) { + event.preventDefault(); + const message = this.state.message; + + if (message.length === 0) { + alert('Please enter a message'); + return; + } + + try { + // Todo: send comment to server + + // Clear message on success + this.setState({message: ''}); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + + handleChange(event) { + this.setState({message: event.target.value}); + } + + render() { + return ( + +
+ +
+