0
Fork 0
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:
Simon Backx 2022-07-21 16:59:12 +02:00
parent 15b534f7d3
commit 2980e58201
10 changed files with 156 additions and 77 deletions

View file

@ -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>
);

View file

@ -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 */

View file

@ -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} />
</>
);
};

View 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>
);
}

View file

@ -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>
</>
);
};

View file

@ -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>
);
};

View file

@ -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 {
);
}
}
*/

View file

@ -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>
);
};

View file

@ -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;

View 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;