From e7c383251bcac775b1372a7d5efa748b21c6e068 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 12 Dec 2025 11:11:47 +0100 Subject: [PATCH] [Glitch] Wrapstodon: Add nav modal Port e206b0d0dead4cf48e211f9e57724ae63d3d8b08 to glitch-soc Signed-off-by: Claire --- .../annual_report/announcement/index.tsx | 2 +- .../announcement/styles.module.scss | 5 ++ .../features/annual_report/index.module.scss | 4 ++ .../glitch/features/annual_report/index.tsx | 29 ++++++---- .../glitch/features/annual_report/modal.tsx | 56 ++++++++++++++++--- .../features/annual_report/nav_item.tsx | 55 ++++++++++++++++++ .../features/navigation_panel/index.tsx | 4 ++ 7 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/annual_report/nav_item.tsx diff --git a/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx b/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx index bd9f01c43c..2ab11a9638 100644 --- a/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/announcement/index.tsx @@ -11,7 +11,7 @@ export interface AnnualReportAnnouncementProps { year: string; state: Exclude; onRequestBuild: () => void; - onOpen: () => void; + onOpen?: () => void; // This is optional when inside the modal, as it won't be shown then. onDismiss: () => void; } diff --git a/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss b/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss index b96ca2e679..9ec62fa0fd 100644 --- a/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss +++ b/app/javascript/flavours/glitch/features/annual_report/announcement/styles.module.scss @@ -16,6 +16,7 @@ var(--color-bg-primary); border-bottom: 1px solid var(--color-border-primary); position: relative; + pointer-events: all; h2 { font-size: 20px; @@ -34,4 +35,8 @@ right: 8px; margin-inline: 0; } + + :global(.modal-root__modal) & { + border-radius: 16px; + } } diff --git a/app/javascript/flavours/glitch/features/annual_report/index.module.scss b/app/javascript/flavours/glitch/features/annual_report/index.module.scss index 9e9a6464c1..375b2e211e 100644 --- a/app/javascript/flavours/glitch/features/annual_report/index.module.scss +++ b/app/javascript/flavours/glitch/features/annual_report/index.module.scss @@ -332,3 +332,7 @@ $mobile-breakpoint: 540px; left: 0; mix-blend-mode: screen; } + +.navItemBadge { + background: var(--color-bg-brand-soft); +} diff --git a/app/javascript/flavours/glitch/features/annual_report/index.tsx b/app/javascript/flavours/glitch/features/annual_report/index.tsx index b1d7fc5585..76eb28bed5 100644 --- a/app/javascript/flavours/glitch/features/annual_report/index.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/index.tsx @@ -11,7 +11,11 @@ import { closeModal } from '@/flavours/glitch/actions/modal'; import { IconButton } from '@/flavours/glitch/components/icon_button'; import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; import { me } from '@/flavours/glitch/initial_state'; -import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/flavours/glitch/store'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Archetype } from './archetype'; @@ -23,21 +27,26 @@ import { NewPosts } from './new_posts'; const moduleClassNames = classNames.bind(styles); +const accountSelector = createAppSelector( + [(state) => state.accounts, (state) => state.annualReport.report], + (accounts, report) => { + if (me) { + return accounts.get(me); + } + if (report?.schema_version === 2) { + return accounts.get(report.account_id); + } + return undefined; + }, +); + export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({ context = 'standalone', }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const report = useAppSelector((state) => state.annualReport.report); - const account = useAppSelector((state) => { - if (me) { - return state.accounts.get(me); - } - if (report?.schema_version === 2) { - return state.accounts.get(report.account_id); - } - return undefined; - }); + const account = useAppSelector(accountSelector); const close = useCallback(() => { dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); diff --git a/app/javascript/flavours/glitch/features/annual_report/modal.tsx b/app/javascript/flavours/glitch/features/annual_report/modal.tsx index 14d13ac958..7c2cec8d89 100644 --- a/app/javascript/flavours/glitch/features/annual_report/modal.tsx +++ b/app/javascript/flavours/glitch/features/annual_report/modal.tsx @@ -1,11 +1,17 @@ +import type { MouseEventHandler } from 'react'; import { useCallback, useEffect } from 'react'; import classNames from 'classnames'; import { closeModal } from '@/flavours/glitch/actions/modal'; -import { useAppDispatch } from '@/flavours/glitch/store'; +import { + generateReport, + selectWrapstodonYear, +} from '@/flavours/glitch/reducers/slices/annual_report'; +import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; import { AnnualReport } from '.'; +import { AnnualReportAnnouncement } from './announcement'; import styles from './index.module.scss'; const AnnualReportModal: React.FC<{ @@ -15,17 +21,42 @@ const AnnualReportModal: React.FC<{ onChangeBackgroundColor('var(--color-bg-media-base)'); }, [onChangeBackgroundColor]); + const { state } = useAppSelector((state) => state.annualReport); + const year = useAppSelector(selectWrapstodonYear); + + const showAnnouncement = year && state && state !== 'available'; + const dispatch = useAppDispatch(); - const handleCloseModal = useCallback>( + + const handleBuildRequest = useCallback(() => { + void dispatch(generateReport()); + }, [dispatch]); + + const handleClose = useCallback(() => { + dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false })); + }, [dispatch]); + + const handleCloseModal: MouseEventHandler = useCallback( (e) => { - if (e.target === e.currentTarget) - dispatch( - closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }), - ); + if (e.target === e.currentTarget) { + handleClose(); + } }, - [dispatch], + [handleClose], ); + // Auto-close if ineligible + useEffect(() => { + if (state === 'ineligible') { + handleClose(); + } + }, [handleClose, state]); + + if (state === 'ineligible') { + // Not sure how you got here, but don't show anything. + return null; + } + return ( // It's fine not to provide a keyboard handler here since there is a global // [Esc] key listener that will close open modals. @@ -40,7 +71,16 @@ const AnnualReportModal: React.FC<{ )} onClick={handleCloseModal} > - + {!showAnnouncement ? ( + + ) : ( + + )} ); }; diff --git a/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx b/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx new file mode 100644 index 0000000000..f67b99f01a --- /dev/null +++ b/app/javascript/flavours/glitch/features/annual_report/nav_item.tsx @@ -0,0 +1,55 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { openModal } from '@/flavours/glitch/actions/modal'; +import { Icon } from '@/flavours/glitch/components/icon'; +import { selectWrapstodonYear } from '@/flavours/glitch/reducers/slices/annual_report'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/flavours/glitch/store'; +import IconPlanet from '@/images/icons/icon_planet.svg?react'; + +import classes from './index.module.scss'; + +const selectReportModalOpen = createAppSelector( + [(state) => state.modal.getIn(['stack', 0, 'modalType'])], + (modalType) => modalType === 'ANNUAL_REPORT', +); + +export const AnnualReportNavItem: FC = () => { + const { state } = useAppSelector((state) => state.annualReport); + const year = useAppSelector(selectWrapstodonYear); + const active = useAppSelector(selectReportModalOpen); + + const dispatch = useAppDispatch(); + const handleClick = useCallback(() => { + dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} })); + }, [dispatch]); + + if (!year || !state || state === 'ineligible') { + return null; + } + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/navigation_panel/index.tsx b/app/javascript/flavours/glitch/features/navigation_panel/index.tsx index a14316df6e..bc19e20b5a 100644 --- a/app/javascript/flavours/glitch/features/navigation_panel/index.tsx +++ b/app/javascript/flavours/glitch/features/navigation_panel/index.tsx @@ -51,6 +51,8 @@ import { canViewFeed } from 'flavours/glitch/permissions'; import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; +import { AnnualReportNavItem } from '../annual_report/nav_item'; + import { DisabledAccountBanner } from './components/disabled_account_banner'; import { FollowedTagsPanel } from './components/followed_tags_panel'; import { ListPanel } from './components/list_panel'; @@ -318,6 +320,8 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({ + +