mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Updated Popup Modals now using global context
This commit is contained in:
parent
15b534f7d3
commit
2980e58201
10 changed files with 156 additions and 77 deletions
|
@ -7,6 +7,7 @@ import AppContext from './AppContext';
|
|||
import {hasMode} from './utils/check-mode';
|
||||
import setupGhostApi from './utils/api';
|
||||
import CommentsBox from './components/CommentsBox';
|
||||
import PopupModal from './components/PopupModal';
|
||||
|
||||
function AuthFrame({adminUrl, onLoad}) {
|
||||
const iframeStyle = {
|
||||
|
@ -56,6 +57,7 @@ export default class App extends React.Component {
|
|||
popupNotification: null,
|
||||
customSiteUrl: props.customSiteUrl,
|
||||
postId: props.postId,
|
||||
popup: null,
|
||||
accentColor: props.accentColor
|
||||
};
|
||||
}
|
||||
|
@ -259,7 +261,7 @@ export default class App extends React.Component {
|
|||
|
||||
/**Get final App level context from App state*/
|
||||
getContextFromState() {
|
||||
const {action, popupNotification, customSiteUrl, member, comments, pagination, postId, admin} = this.state;
|
||||
const {action, popupNotification, customSiteUrl, member, comments, pagination, postId, admin, popup} = this.state;
|
||||
return {
|
||||
action,
|
||||
popupNotification,
|
||||
|
@ -274,6 +276,7 @@ export default class App extends React.Component {
|
|||
accentColor: this.props.accentColor,
|
||||
commentsEnabled: this.props.commentsEnabled,
|
||||
appVersion: this.props.appVersion,
|
||||
popup,
|
||||
dispatchAction: (_action, data) => this.dispatchAction(_action, data),
|
||||
|
||||
/**
|
||||
|
@ -297,7 +300,7 @@ export default class App extends React.Component {
|
|||
<AppContext.Provider value={this.getContextFromState()}>
|
||||
<CommentsBoxContainer done={done} />
|
||||
<AuthFrame adminUrl={this.props.adminUrl} onLoad={this.initSetup.bind(this)} initStatus={this.state.initStatus}/>
|
||||
<div id="ghost-comments-modal-root"></div>
|
||||
<PopupModal />
|
||||
</AppContext.Provider>
|
||||
</SentryErrorBoundary>
|
||||
);
|
||||
|
|
|
@ -281,6 +281,18 @@ async function updateMemberName({data, state, api}) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function openPopup({data}) {
|
||||
return {
|
||||
popup: data
|
||||
};
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
return {
|
||||
popup: null
|
||||
};
|
||||
}
|
||||
|
||||
const Actions = {
|
||||
// Put your actions here
|
||||
addComment,
|
||||
|
@ -293,7 +305,9 @@ const Actions = {
|
|||
reportComment,
|
||||
addReply,
|
||||
loadMoreComments,
|
||||
updateMemberName
|
||||
updateMemberName,
|
||||
openPopup,
|
||||
closePopup
|
||||
};
|
||||
|
||||
/** Handle actions in the App, returns updated state */
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import React, {useContext, useEffect, useState, useRef} from 'react';
|
||||
import React, {useContext, useEffect, useRef} from 'react';
|
||||
import {Transition} from '@headlessui/react';
|
||||
import AppContext from '../AppContext';
|
||||
import Avatar from './Avatar';
|
||||
import {useEditor, EditorContent} from '@tiptap/react';
|
||||
import {getEditorConfig} from '../utils/editor';
|
||||
import AddNameDialog from './modals/AddNameDialog';
|
||||
|
||||
const Form = (props) => {
|
||||
const {member, postId, dispatchAction, onAction, avatarSaturation} = useContext(AppContext);
|
||||
const [isAddNameShowing, setAddNameShowing] = useState(false);
|
||||
const formEl = useRef(null);
|
||||
|
||||
let config;
|
||||
|
@ -172,19 +170,13 @@ const Form = (props) => {
|
|||
if (memberName) {
|
||||
editor.commands.focus();
|
||||
} else {
|
||||
setAddNameShowing(true);
|
||||
dispatchAction('openPopup', {
|
||||
type: 'addNameDialog',
|
||||
callback: () => editor.commands.focus()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const closeAddName = () => {
|
||||
setAddNameShowing(false);
|
||||
};
|
||||
|
||||
const saveAddName = () => {
|
||||
setAddNameShowing(false);
|
||||
editor.commands.focus();
|
||||
};
|
||||
|
||||
let submitText;
|
||||
if (props.isReply) {
|
||||
submitText = 'Add reply';
|
||||
|
@ -264,7 +256,6 @@ const Form = (props) => {
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<AddNameDialog show={isAddNameShowing} submit={saveAddName} cancel={closeAddName} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
39
apps/comments-ui/src/components/PopupModal.js
Normal file
39
apps/comments-ui/src/components/PopupModal.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {useContext, useEffect, useState} from 'react';
|
||||
import AppContext from '../AppContext';
|
||||
import Pages from '../pages';
|
||||
import GenericDialog from './modals/GenericDialog';
|
||||
|
||||
export default function PopupModal(props) {
|
||||
const {popup} = useContext(AppContext);
|
||||
|
||||
// To make sure we can properly animate a popup that goes away, we keep a state of the last visible popup
|
||||
// This way, when the popup context is set to null, we still can show the popup while we transition it away
|
||||
const [lastPopup, setLastPopup] = useState(popup);
|
||||
|
||||
useEffect(() => {
|
||||
if (popup !== null) {
|
||||
setLastPopup(popup);
|
||||
}
|
||||
}, [popup, setLastPopup]);
|
||||
|
||||
if (!lastPopup || !lastPopup.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {type, ...popupProps} = lastPopup;
|
||||
const PageComponent = Pages[type];
|
||||
|
||||
if (!PageComponent) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unknown popup of type ', type);
|
||||
return null;
|
||||
}
|
||||
|
||||
const show = popup === lastPopup;
|
||||
|
||||
return (
|
||||
<GenericDialog show={show}>
|
||||
<PageComponent {...popupProps}/>
|
||||
</GenericDialog>
|
||||
);
|
||||
}
|
|
@ -1,12 +1,24 @@
|
|||
import React, {useContext, useState} from 'react';
|
||||
import AppContext from '../../AppContext';
|
||||
import GenericDialog from './GenericDialog';
|
||||
|
||||
const AddNameDialog = (props) => {
|
||||
const {dispatchAction} = useContext(AppContext);
|
||||
|
||||
const close = () => {
|
||||
dispatchAction('closePopup');
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
await dispatchAction('updateMemberName', {
|
||||
name
|
||||
});
|
||||
props.callback();
|
||||
close();
|
||||
};
|
||||
|
||||
const [name, setName] = useState('');
|
||||
return (
|
||||
<GenericDialog show={props.show} cancel={props.cancel}>
|
||||
<>
|
||||
<h1 className="font-sans font-bold tracking-tight text-[24px] mb-3 text-black">Add context to your comment</h1>
|
||||
<p className="font-sans text-[1.45rem] text-neutral-500 px-8 leading-9">For a healthy discussion, let other members know who you are when commenting.</p>
|
||||
<section className="mt-8 text-left">
|
||||
|
@ -28,17 +40,12 @@ const AddNameDialog = (props) => {
|
|||
/>
|
||||
<button
|
||||
className="transition duration-200 ease-linear w-full h-[44px] mt-4 px-8 flex items-center justify-center rounded-md text-white font-sans font-semibold text-[15px] bg-blue-700"
|
||||
onClick={() => {
|
||||
dispatchAction('updateMemberName', {
|
||||
name
|
||||
});
|
||||
props.submit();
|
||||
}}
|
||||
onClick={submit}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</section>
|
||||
</GenericDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import React from 'react';
|
||||
import React, {useContext} from 'react';
|
||||
import {Transition} from '@headlessui/react';
|
||||
import Modal from './Modal';
|
||||
import Frame from '../Frame';
|
||||
import AppContext from '../../AppContext';
|
||||
|
||||
const GenericDialog = (props) => {
|
||||
// The modal will cover the whole screen, so while it is hidden, we need to disable pointer events
|
||||
const {dispatchAction} = useContext(AppContext);
|
||||
|
||||
const close = (event) => {
|
||||
dispatchAction('closePopup');
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition show={props.show}>
|
||||
<Modal>
|
||||
<Transition show={props.show} appear={true}>
|
||||
<Frame type="fixed">
|
||||
<div>
|
||||
<Transition.Child
|
||||
enter="transition duration-200 linear"
|
||||
|
@ -16,7 +23,7 @@ const GenericDialog = (props) => {
|
|||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed top-0 left-0 overflow-hidden w-screen h-screen flex pt-12 justify-center bg-gradient-to-b from-[rgba(0,0,0,0.2)] to-rgba(0,0,0,0.1) backdrop-blur-[2px]" onClick={props.cancel}>
|
||||
<div className="fixed top-0 left-0 overflow-hidden w-screen h-screen flex pt-12 justify-center bg-gradient-to-b from-[rgba(0,0,0,0.2)] to-rgba(0,0,0,0.1) backdrop-blur-[2px]" onClick={close}>
|
||||
<Transition.Child
|
||||
enter="transition duration-200 delay-150 linear"
|
||||
enterFrom="translate-y-4 opacity-0"
|
||||
|
@ -32,7 +39,7 @@ const GenericDialog = (props) => {
|
|||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Modal>
|
||||
</Frame>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import {Component} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
import Frame from '../Frame';
|
||||
|
||||
/**
|
||||
* Full screen iframe, that is displayed fixed, and that can be used anywhere ('portalled' outside of existing iframes)
|
||||
*/
|
||||
export default function Modal(props) {
|
||||
return (
|
||||
<Frame type="fixed" style={props.style}>
|
||||
{props.children}
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
export default class Modal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -41,3 +45,4 @@ export default class Modal extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,36 +1,15 @@
|
|||
import React, {useContext, useState} from 'react';
|
||||
import ReportDialog from './ReportDialog';
|
||||
import React, {useContext} from 'react';
|
||||
import AppContext from '../../AppContext';
|
||||
|
||||
const NotAuthorContextMenu = (props) => {
|
||||
const {dispatchAction} = useContext(AppContext);
|
||||
let [isOpen, setOpen] = useState(false);
|
||||
let [reportProgress, setProgress] = useState('default'); // states for button: default, sending, sent
|
||||
|
||||
const openModal = () => {
|
||||
setProgress('default');
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setProgress('default');
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const reportComment = (event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
setProgress('sending');
|
||||
|
||||
// purposely faking the timing of the report being sent for user feedback purposes
|
||||
setTimeout(() => {
|
||||
setProgress('sent');
|
||||
dispatchAction('reportComment', props.comment);
|
||||
|
||||
setTimeout(() => {
|
||||
setOpen(false);
|
||||
}, 750);
|
||||
}, 1000);
|
||||
dispatchAction('openPopup', {
|
||||
type: 'reportDialog',
|
||||
comment: props.comment
|
||||
});
|
||||
props.close();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -38,7 +17,6 @@ const NotAuthorContextMenu = (props) => {
|
|||
<button className="text-[14px]" onClick={openModal}>
|
||||
Report comment
|
||||
</button>
|
||||
<ReportDialog show={isOpen} progress={reportProgress} submit={reportComment} cancel={closeModal} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,43 +1,67 @@
|
|||
import React from 'react';
|
||||
import GenericDialog from './GenericDialog';
|
||||
import React, {useContext, useState} from 'react';
|
||||
import {ReactComponent as SpinnerIcon} from '../../images/icons/spinner.svg';
|
||||
import {ReactComponent as SuccessIcon} from '../../images/icons/success.svg';
|
||||
import AppContext from '../../AppContext';
|
||||
|
||||
const ReportDialog = (props) => {
|
||||
const [progress, setProgress] = useState('default');
|
||||
|
||||
let buttonColor = 'bg-red-600';
|
||||
if (props.progress === 'sent') {
|
||||
if (progress === 'sent') {
|
||||
buttonColor = 'bg-green-600';
|
||||
}
|
||||
|
||||
let buttonText = 'Yes';
|
||||
if (props.progress === 'sending') {
|
||||
if (progress === 'sending') {
|
||||
buttonText = 'Sending';
|
||||
} else if (props.progress === 'sent') {
|
||||
} else if (progress === 'sent') {
|
||||
buttonText = 'Sent';
|
||||
}
|
||||
|
||||
let buttonIcon = null;
|
||||
if (props.progress === 'sending') {
|
||||
if (progress === 'sending') {
|
||||
buttonIcon = <SpinnerIcon className="w-[20px] h-[20px] mr-2 fill-white" />;
|
||||
} else if (props.progress === 'sent') {
|
||||
} else if (progress === 'sent') {
|
||||
buttonIcon = <SuccessIcon className="w-[16px] h-[16px] mr-2" />;
|
||||
}
|
||||
|
||||
const {dispatchAction} = useContext(AppContext);
|
||||
|
||||
const close = (event) => {
|
||||
dispatchAction('closePopup');
|
||||
};
|
||||
|
||||
const submit = (event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
setProgress('sending');
|
||||
|
||||
// purposely faking the timing of the report being sent for user feedback purposes
|
||||
setTimeout(() => {
|
||||
setProgress('sent');
|
||||
dispatchAction('reportComment', props.comment);
|
||||
|
||||
setTimeout(() => {
|
||||
close();
|
||||
}, 750);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDialog show={props.show} cancel={props.cancel}>
|
||||
<>
|
||||
<h1 className="font-sans font-bold tracking-tight text-[24px] mb-3 text-black">You sure you want to report?</h1>
|
||||
<p className="font-sans text-[1.45rem] text-neutral-500">You request will be sent to the owner of this site.</p>
|
||||
<div className="mt-10">
|
||||
<button
|
||||
className={`transition duration-200 ease-linear w-full h-[44px] px-8 flex items-center justify-center rounded-md text-white font-sans font-semibold text-[15px] ${buttonColor}`}
|
||||
onClick={props.submit}
|
||||
onClick={submit}
|
||||
>
|
||||
{buttonIcon}{buttonText}
|
||||
</button>
|
||||
<p className="font-sans font-medium text-[1.45rem] text-neutral-500 mt-4 -mb-1">No, <button className="font-sans underline" onClick={props.cancel}>I've changed my mind</button></p>
|
||||
<p className="font-sans font-medium text-[1.45rem] text-neutral-500 mt-4 -mb-1">No, <button className="font-sans underline" onClick={close}>I've changed my mind</button></p>
|
||||
</div>
|
||||
</GenericDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportDialog;
|
||||
export default ReportDialog;
|
||||
|
|
11
apps/comments-ui/src/pages.js
Normal file
11
apps/comments-ui/src/pages.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import AddNameDialog from './components/modals/AddNameDialog';
|
||||
import ReportDialog from './components/modals/ReportDialog';
|
||||
|
||||
/** List of all available pages in Comments-UI, mapped to their UI component
|
||||
* Any new page added to comments-ui needs to be mapped here
|
||||
*/
|
||||
const Pages = {
|
||||
addNameDialog: AddNameDialog,
|
||||
reportDialog: ReportDialog
|
||||
};
|
||||
export default Pages;
|
Loading…
Add table
Reference in a new issue