mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-23 11:55:01 +03:00
Added AppContext and basic Form component
This commit is contained in:
parent
fccc18f51c
commit
6f0defc6a1
@ -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",
|
||||
|
@ -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 (
|
||||
<Sentry.ErrorBoundary>
|
||||
{children}
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="App">
|
||||
Hello world
|
||||
</div>
|
||||
<>
|
||||
{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 (
|
||||
<SentryErrorBoundary dsn={this.props.sentryDsn}>
|
||||
<AppContext.Provider value={this.getContextFromState()}>
|
||||
<Form />
|
||||
</AppContext.Provider>
|
||||
</SentryErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
15
apps/comments-ui/src/AppContext.js
Normal file
15
apps/comments-ui/src/AppContext.js
Normal file
@ -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;
|
12
apps/comments-ui/src/actions.js
Normal file
12
apps/comments-ui/src/actions.js
Normal file
@ -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 {};
|
||||
}
|
54
apps/comments-ui/src/components/Form.js
Normal file
54
apps/comments-ui/src/components/Form.js
Normal file
@ -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 (
|
||||
<form onSubmit={this.submitForm} className="comment-form">
|
||||
<figure className="avatar">
|
||||
<span />
|
||||
</figure>
|
||||
<textarea value={this.state.message} onChange={this.handleChange} placeholder="What are your thoughts?" />
|
||||
<button type="submit" className="button primary">Comment</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form;
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
|
||||
import App from './App';
|
||||
const ROOT_DIV_ID = 'ghost-comments-root';
|
||||
|
||||
function addRootDiv() {
|
||||
@ -27,7 +27,8 @@ function getSiteData() {
|
||||
const siteUrl = scriptTag.dataset.ghost;
|
||||
const apiKey = scriptTag.dataset.key;
|
||||
const apiUrl = scriptTag.dataset.api;
|
||||
return {siteUrl, apiKey, apiUrl};
|
||||
const sentryDsn = scriptTag.dataset.sentryDsn;
|
||||
return {siteUrl, apiKey, apiUrl, sentryDsn};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@ -47,13 +48,12 @@ function setup({siteUrl}) {
|
||||
|
||||
function init() {
|
||||
// const customSiteUrl = getSiteUrl();
|
||||
const {siteUrl: customSiteUrl} = getSiteData();
|
||||
const {siteUrl: customSiteUrl, sentryDsn} = getSiteData();
|
||||
const siteUrl = customSiteUrl || window.location.origin;
|
||||
setup({siteUrl});
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
Hello World
|
||||
{/* <App siteUrl={siteUrl} customSiteUrl={customSiteUrl} apiKey={apiKey} apiUrl={apiUrl} /> */}
|
||||
{<App siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn}/>}
|
||||
</React.StrictMode>,
|
||||
document.getElementById(ROOT_DIV_ID)
|
||||
);
|
||||
|
22
apps/comments-ui/src/utils/check-mode.js
Normal file
22
apps/comments-ui/src/utils/check-mode.js
Normal file
@ -0,0 +1,22 @@
|
||||
export const isDevMode = function ({customSiteUrl = ''} = {}) {
|
||||
if (customSiteUrl && process.env.NODE_ENV === 'development') {
|
||||
return false;
|
||||
}
|
||||
return (process.env.NODE_ENV === 'development');
|
||||
};
|
||||
|
||||
export const isTestMode = function () {
|
||||
return (process.env.NODE_ENV === 'test');
|
||||
};
|
||||
|
||||
const modeFns = {
|
||||
dev: isDevMode,
|
||||
test: isTestMode
|
||||
};
|
||||
|
||||
export const hasMode = (modes = [], options = {}) => {
|
||||
return modes.some((mode) => {
|
||||
const modeFn = modeFns[mode];
|
||||
return !!(modeFn && modeFn(options));
|
||||
});
|
||||
};
|
16
apps/comments-ui/src/utils/helpers.js
Normal file
16
apps/comments-ui/src/utils/helpers.js
Normal file
@ -0,0 +1,16 @@
|
||||
export const createPopupNotification = ({type, status, autoHide, duration = 2600, closeable, state, message, meta = {}}) => {
|
||||
let count = 0;
|
||||
if (state && state.popupNotification) {
|
||||
count = (state.popupNotification.count || 0) + 1;
|
||||
}
|
||||
return {
|
||||
type,
|
||||
status,
|
||||
autoHide,
|
||||
closeable,
|
||||
duration,
|
||||
meta,
|
||||
message,
|
||||
count
|
||||
};
|
||||
};
|
@ -1503,6 +1503,59 @@
|
||||
estree-walker "^1.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@sentry/browser@7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.5.0.tgz#1ac651117625c732de58cfbd46dd9cf302212e42"
|
||||
integrity sha512-tTtccbqYti8liTuLudTI0Qrgpe3sKajm0lwsMN4tb1YE879a9JiQ6U6kZ1G/fOIMjOm09pE7J8ozQ+FihPHw5g==
|
||||
dependencies:
|
||||
"@sentry/core" "7.5.0"
|
||||
"@sentry/types" "7.5.0"
|
||||
"@sentry/utils" "7.5.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.5.0.tgz#4ccc2312017fc6158cc379f5828dc6bbe2cdf1f7"
|
||||
integrity sha512-2KO2hVUki3WgvPlB0qj9+yea56CmsK2b1XtBSyAnqbs+JiXWgerF4qshVsH52kS/1h2B0CisyeIv64/WfuGvQQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "7.5.0"
|
||||
"@sentry/types" "7.5.0"
|
||||
"@sentry/utils" "7.5.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.5.0.tgz#30801accb9475cc3f155802a3fefd218d66fbfda"
|
||||
integrity sha512-R3jGEOtRtZaYCswSNs/7SmjOj/Pp8BhRyXk4q0a5GXghbuVAdzZvlJH0XnD/6jOJAF0iSXFuyGSLqVUmjkY9Ow==
|
||||
dependencies:
|
||||
"@sentry/types" "7.5.0"
|
||||
"@sentry/utils" "7.5.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.5.0.tgz#52b9d611995a9538f36d3b976cfc19abb40c5375"
|
||||
integrity sha512-kkSRRK5WIrsDzpbTJBmWqKxpOkmtRTjtEAQjJ8+5iIbfFYRjv3kp58aN2v7+7W4XrITkrht3wMhB2W6p+hzxfQ==
|
||||
dependencies:
|
||||
"@sentry/browser" "7.5.0"
|
||||
"@sentry/types" "7.5.0"
|
||||
"@sentry/utils" "7.5.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.5.0.tgz#610f14c1219ba461ca84a3c89e06de8c0cf357bc"
|
||||
integrity sha512-VPQ/53mLo5N8NQUB4k6R2GQBWoW8otFyhhPnC75gYXeBTItVCzJAylVyWy8b+gGqGst+pQN3wb2dl9xhrd69YQ==
|
||||
|
||||
"@sentry/utils@7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.5.0.tgz#64435ea094aa7d79d1dfe7586d2d5a2bff9e3839"
|
||||
integrity sha512-DgHrkGgHplVMgMbU9hGBfGBV6LcOwNBrhHiVaFwo2NHiXnGwMkaILi5XTRjKm9Iu/m2choAFABA80HEtPKmjtA==
|
||||
dependencies:
|
||||
"@sentry/types" "7.5.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sinclair/typebox@^0.23.3":
|
||||
version "0.23.5"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d"
|
||||
@ -5776,6 +5829,13 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
|
||||
@ -9514,7 +9574,7 @@ react-error-overlay@6.0.9, react-error-overlay@^6.0.9:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||
|
||||
react-is@^16.13.1:
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@ -11141,7 +11201,7 @@ tsconfig-paths@^3.14.1:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^1.8.1:
|
||||
tslib@^1.8.1, tslib@^1.9.3:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
Loading…
Reference in New Issue
Block a user