From 9bfbd5b3b97a8dfd156e72aa55238989d50cc7f2 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Mon, 14 Aug 2023 14:11:53 +0200 Subject: [PATCH] History log in AdminX (#17666) refs. https://github.com/TryGhost/Product/issues/3710 --------- Co-authored-by: Jono Mingard --- apps/admin-x-settings/src/App.tsx | 7 +- .../src/admin-x-ds/assets/icons/pen.svg | 1 + .../global/InfiniteScrollListener.stories.tsx | 31 +++ .../global/InfiniteScrollListener.tsx | 33 +++ .../src/admin-x-ds/global/Menu.stories.tsx | 22 +- .../src/admin-x-ds/global/Menu.tsx | 55 ++--- .../src/admin-x-ds/global/Popover.stories.tsx | 34 +++ .../src/admin-x-ds/global/Popover.tsx | 92 ++++++++ .../admin-x-ds/global/modal/Modal.stories.tsx | 18 ++ .../src/admin-x-ds/global/modal/Modal.tsx | 15 +- apps/admin-x-settings/src/api/actions.ts | 217 ++++++++++++++++++ .../components/providers/RoutingProvider.tsx | 40 +++- .../settings/advanced/AdvancedSettings.tsx | 5 +- .../components/settings/advanced/History.tsx | 24 ++ .../settings/advanced/HistoryModal.tsx | 217 ++++++++++++++++++ .../email/newsletters/AddNewsletterModal.tsx | 2 +- .../email/newsletters/NewslettersList.tsx | 2 +- .../settings/general/UserDetailModal.tsx | 2 +- .../src/components/settings/general/Users.tsx | 4 +- .../settings/membership/tiers/TiersList.tsx | 2 +- apps/admin-x-settings/src/main.tsx | 1 + .../admin-x-settings/src/utils/apiRequests.ts | 44 +++- .../test/e2e/advanced/history.test.ts | 45 ++++ .../test/e2e/general/users/actions.test.ts | 11 +- .../test/e2e/site/theme.test.ts | 6 +- apps/admin-x-settings/test/utils/e2e.ts | 26 ++- .../test/utils/responses/actions.json | 96 ++++++++ .../admin/app/components/admin-x/settings.js | 6 + 28 files changed, 954 insertions(+), 104 deletions(-) create mode 100644 apps/admin-x-settings/src/admin-x-ds/assets/icons/pen.svg create mode 100644 apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.stories.tsx create mode 100644 apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.tsx create mode 100644 apps/admin-x-settings/src/admin-x-ds/global/Popover.stories.tsx create mode 100644 apps/admin-x-settings/src/admin-x-ds/global/Popover.tsx create mode 100644 apps/admin-x-settings/src/api/actions.ts create mode 100644 apps/admin-x-settings/src/components/settings/advanced/History.tsx create mode 100644 apps/admin-x-settings/src/components/settings/advanced/HistoryModal.tsx create mode 100644 apps/admin-x-settings/test/e2e/advanced/history.test.ts create mode 100644 apps/admin-x-settings/test/utils/responses/actions.json diff --git a/apps/admin-x-settings/src/App.tsx b/apps/admin-x-settings/src/App.tsx index 2d4ab477e1..49909e15db 100644 --- a/apps/admin-x-settings/src/App.tsx +++ b/apps/admin-x-settings/src/App.tsx @@ -2,7 +2,7 @@ import ExitSettingsButton from './components/ExitSettingsButton'; import GlobalDataProvider from './components/providers/GlobalDataProvider'; import Heading from './admin-x-ds/global/Heading'; import NiceModal from '@ebay/nice-modal-react'; -import RoutingProvider from './components/providers/RoutingProvider'; +import RoutingProvider, {ExternalLink} from './components/providers/RoutingProvider'; import Settings from './components/Settings'; import Sidebar from './components/Sidebar'; import {GlobalDirtyStateProvider} from './hooks/useGlobalDirtyState'; @@ -13,6 +13,7 @@ import {Toaster} from 'react-hot-toast'; interface AppProps { ghostVersion: string; officialThemes: OfficialTheme[]; + externalNavigate: (link: ExternalLink) => void; } const queryClient = new QueryClient({ @@ -25,12 +26,12 @@ const queryClient = new QueryClient({ } }); -function App({ghostVersion, officialThemes}: AppProps) { +function App({ghostVersion, officialThemes, externalNavigate}: AppProps) { return ( - +
pencil \ No newline at end of file diff --git a/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.stories.tsx b/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.stories.tsx new file mode 100644 index 0000000000..3049f2dca0 --- /dev/null +++ b/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.stories.tsx @@ -0,0 +1,31 @@ +import {useState} from 'react'; +import type {Meta, StoryObj} from '@storybook/react'; + +import InfiniteScrollListener from './InfiniteScrollListener'; + +const meta = { + title: 'Global / Infinite scroll listener', + component: InfiniteScrollListener, + tags: ['autodocs'] +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + offset: 250 + }, + render: function Component(args) { + const [wasTriggered, setTriggered] = useState(false); + + return
+
Try scrolling here ... {wasTriggered && Near the end, time to load the next page!}
+
+
+ setTriggered(true)} /> +
+
+
; + } +}; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.tsx b/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.tsx new file mode 100644 index 0000000000..632bc75ac9 --- /dev/null +++ b/apps/admin-x-settings/src/admin-x-ds/global/InfiniteScrollListener.tsx @@ -0,0 +1,33 @@ +import React, {useEffect, useRef} from 'react'; + +/** + * Triggers a callback when the user scrolls close to the end of an element + * (exactly how close is configurable with `offset`). The parent element must have + * position: relative/absolute/etc. + */ +const InfiniteScrollListener: React.FC<{ + /** How many pixels before the end of the container the callback should trigger */ + offset: number + onTrigger: () => void +}> = ({offset, onTrigger}) => { + const ref = useRef(null); + + useEffect(() => { + const intersectionObserver = new IntersectionObserver((entries) => { + if (entries[0].intersectionRatio <= 0) { + return; + } + onTrigger(); + }); + + if (ref.current) { + intersectionObserver.observe(ref.current); + } + + return () => intersectionObserver.disconnect(); + }, [onTrigger]); + + return
; +}; + +export default InfiniteScrollListener; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/Menu.stories.tsx b/apps/admin-x-settings/src/admin-x-ds/global/Menu.stories.tsx index 6ea3b5f8b8..d6c4a568ba 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/Menu.stories.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/Menu.stories.tsx @@ -1,4 +1,3 @@ -import {ReactNode} from 'react'; import type {Meta, StoryObj} from '@storybook/react'; import Button from './Button'; @@ -7,8 +6,7 @@ import Menu from './Menu'; const meta = { title: 'Global / Menu', component: Menu, - tags: ['autodocs'], - decorators: [(_story: () => ReactNode) => (
{_story()}
)] + tags: ['autodocs'] } satisfies Meta; export default meta; @@ -22,15 +20,9 @@ const items = [ }} ]; -const longItems = [ - {id: 'item-1', label: 'This is a really, really long item that nobody should be using but oh well'}, - {id: 'item-2', label: 'Item 2'}, - {id: 'item-3', label: 'Item 3'} -]; - export const Default: Story = { args: { - trigger: , + trigger: , items: items, position: 'left' }, @@ -43,7 +35,7 @@ export const Default: Story = { export const Right: Story = { args: { - trigger: , + trigger: , items: items, position: 'right' }, @@ -53,11 +45,3 @@ export const Right: Story = { ) ] }; - -export const LongLabels: Story = { - args: { - trigger: , - items: longItems, - position: 'right' - } -}; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/Menu.tsx b/apps/admin-x-settings/src/admin-x-ds/global/Menu.tsx index b76fb7180e..ea174f2764 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/Menu.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/Menu.tsx @@ -1,6 +1,6 @@ import Button, {ButtonProps, ButtonSize} from './Button'; -import React, {useState} from 'react'; -import clsx from 'clsx'; +import Popover, {PopoverPosition} from './Popover'; +import React from 'react'; export type MenuItem = { id: string, @@ -8,57 +8,32 @@ export type MenuItem = { onClick?: () => void } -type MenuPosition = 'left' | 'right'; - interface MenuProps { trigger?: React.ReactNode; triggerButtonProps?: ButtonProps; triggerSize?: ButtonSize; items: MenuItem[]; - position?: MenuPosition; - className?: string; + position?: PopoverPosition; } -const Menu: React.FC = ({trigger, triggerButtonProps, items, position, className}) => { - const [menuOpen, setMenuOpen] = useState(false); - - const toggleMenu = () => { - setMenuOpen(!menuOpen); - }; - - const handleBackdropClick = (e: React.MouseEvent) => { - if (e.target === e.currentTarget) { - setMenuOpen(false); - } - }; - +const Menu: React.FC = ({ + trigger, + triggerButtonProps, + items, + position = 'left' +}) => { if (!trigger) { trigger = + ))}
- {/* Menu List */} -
-
- {items.map(item => ( - - ))} -
-
-
+ ); }; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/Popover.stories.tsx b/apps/admin-x-settings/src/admin-x-ds/global/Popover.stories.tsx new file mode 100644 index 0000000000..d6245ef9f0 --- /dev/null +++ b/apps/admin-x-settings/src/admin-x-ds/global/Popover.stories.tsx @@ -0,0 +1,34 @@ +import type {Meta, StoryObj} from '@storybook/react'; + +// import BoilerPlate from './Boilerplate'; +import Button from './Button'; +import Popover from './Popover'; + +const meta = { + title: 'Global / Popover', + component: Popover, + tags: ['autodocs'], + argTypes: { + trigger: { + control: { + type: 'text' + } + } + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + trigger: ( +