[Glitch] Wrapstodon: Add nav modal

Port e206b0d0de to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2025-12-12 11:11:47 +01:00
committed by Claire
parent febd6241bf
commit e7c383251b
7 changed files with 136 additions and 19 deletions

View File

@@ -11,7 +11,7 @@ export interface AnnualReportAnnouncementProps {
year: string;
state: Exclude<ApiAnnualReportState, 'ineligible'>;
onRequestBuild: () => void;
onOpen: () => void;
onOpen?: () => void; // This is optional when inside the modal, as it won't be shown then.
onDismiss: () => void;
}

View File

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

View File

@@ -332,3 +332,7 @@ $mobile-breakpoint: 540px;
left: 0;
mix-blend-mode: screen;
}
.navItemBadge {
background: var(--color-bg-brand-soft);
}

View File

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

View File

@@ -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<React.MouseEventHandler<HTMLDivElement>>(
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}
>
<AnnualReport context='modal' />
{!showAnnouncement ? (
<AnnualReport context='modal' />
) : (
<AnnualReportAnnouncement
year={year.toString()}
state={state}
onDismiss={handleClose}
onRequestBuild={handleBuildRequest}
/>
)}
</div>
);
};

View File

@@ -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 (
<button
type='button'
className={classNames('column-link column-link--transparent', { active })}
onClick={handleClick}
>
<Icon icon={IconPlanet} id='wrapstodon-planet' width='24' height='24' />
<span>Wrapstodon {year}</span>
<span className={classNames('column-link__badge', classes.navItemBadge)}>
<FormattedMessage
id='annual_report.nav_item.badge'
defaultMessage='New'
/>
</span>
</button>
);
};

View File

@@ -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 }> = ({
<FollowRequestsLink />
<AnnualReportNavItem />
<hr />
<ListPanel />