[Glitch] Wrapstodon: Allow dismissing banner

Port 10f232ca08 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo
2025-12-12 10:40:45 +01:00
committed by Claire
parent 6503287c2d
commit aa45a5fa83
13 changed files with 178 additions and 154 deletions

View File

@@ -1,38 +1,60 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
import { action } from 'storybook/actions';
import type { AnyFunction, OmitValueType } from '@/flavours/glitch/utils/types';
import type { AnnualReportAnnouncementProps } from '.';
import { AnnualReportAnnouncement } from '.';
const meta = {
title: 'Components/AnnualReportAnnouncement',
component: AnnualReportAnnouncement,
args: {
hasData: false,
isLoading: false,
year: '2025',
onRequestBuild: fn(),
onOpen: fn(),
type Props = OmitValueType<
// We can't use the name 'state' here because it's reserved for overriding Redux state.
Omit<AnnualReportAnnouncementProps, 'state'> & {
reportState: AnnualReportAnnouncementProps['state'];
},
} satisfies Meta<typeof AnnualReportAnnouncement>;
AnyFunction // Remove any functions, as they can't meaningfully be controlled in Storybook.
>;
const meta = {
title: 'Components/AnnualReport/Announcement',
args: {
reportState: 'eligible',
year: '2025',
},
argTypes: {
reportState: {
control: {
type: 'select',
},
options: ['eligible', 'generating', 'available'],
},
},
render({ reportState, ...args }: Props) {
return (
<AnnualReportAnnouncement
state={reportState}
{...args}
onDismiss={action('dismissed announcement')}
onOpen={action('opened report modal')}
onRequestBuild={action('requested build')}
/>
);
},
} satisfies Meta<Props>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: (args) => <AnnualReportAnnouncement {...args} />,
};
export const Default: Story = {};
export const Loading: Story = {
args: {
isLoading: true,
reportState: 'generating',
},
render: Default.render,
};
export const WithData: Story = {
args: {
hasData: true,
reportState: 'available',
},
render: Default.render,
};

View File

@@ -2,33 +2,36 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import type { ApiAnnualReportState } from '@/flavours/glitch/api/annual_report';
import { Button } from '@/flavours/glitch/components/button';
import styles from './styles.module.scss';
export const AnnualReportAnnouncement: React.FC<{
export interface AnnualReportAnnouncementProps {
year: string;
hasData: boolean;
isLoading: boolean;
state: Exclude<ApiAnnualReportState, 'ineligible'>;
onRequestBuild: () => void;
onOpen: () => void;
}> = ({ year, hasData, isLoading, onRequestBuild, onOpen }) => {
onDismiss: () => void;
}
export const AnnualReportAnnouncement: React.FC<
AnnualReportAnnouncementProps
> = ({ year, state, onRequestBuild, onOpen, onDismiss }) => {
return (
<div className={classNames('theme-dark', styles.wrapper)}>
<h2>
<FormattedMessage
id='annual_report.announcement.title'
defaultMessage='Wrapstodon {year} has arrived'
values={{ year }}
/>
</h2>
<p>
<FormattedMessage
id='annual_report.announcement.description'
defaultMessage='Discover more about your engagement on Mastodon over the past year.'
/>
</p>
{hasData ? (
<FormattedMessage
id='annual_report.announcement.title'
defaultMessage='Wrapstodon {year} has arrived'
values={{ year }}
tagName='h2'
/>
<FormattedMessage
id='annual_report.announcement.description'
defaultMessage='Discover more about your engagement on Mastodon over the past year.'
tagName='p'
/>
{state === 'available' ? (
<Button onClick={onOpen}>
<FormattedMessage
id='annual_report.announcement.action_view'
@@ -36,13 +39,21 @@ export const AnnualReportAnnouncement: React.FC<{
/>
</Button>
) : (
<Button loading={isLoading} onClick={onRequestBuild}>
<Button loading={state === 'generating'} onClick={onRequestBuild}>
<FormattedMessage
id='annual_report.announcement.action_build'
defaultMessage='Build my Wrapstodon'
/>
</Button>
)}
{state === 'eligible' && (
<Button onClick={onDismiss} plain className={styles.closeButton}>
<FormattedMessage
id='annual_report.announcement.action_dismiss'
defaultMessage='No thanks'
/>
</Button>
)}
</div>
);
};

View File

@@ -15,6 +15,7 @@
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%)
var(--color-bg-primary);
border-bottom: 1px solid var(--color-border-primary);
position: relative;
h2 {
font-size: 20px;
@@ -26,4 +27,11 @@
p {
margin-bottom: 20px;
}
.closeButton {
position: absolute;
bottom: 8px;
right: 8px;
margin-inline: 0;
}
}

View File

@@ -2,6 +2,7 @@ import { useCallback } from 'react';
import type { FC } from 'react';
import { openModal } from '@/flavours/glitch/actions/modal';
import { useDismissible } from '@/flavours/glitch/hooks/useDismissible';
import {
generateReport,
selectWrapstodonYear,
@@ -19,21 +20,26 @@ export const AnnualReportTimeline: FC = () => {
void dispatch(generateReport());
}, [dispatch]);
const { wasDismissed, dismiss } = useDismissible(
`annual_report_announcement_${year}`,
);
const handleOpen = useCallback(() => {
dispatch(openModal({ modalType: 'ANNUAL_REPORT', modalProps: {} }));
}, [dispatch]);
dismiss();
}, [dismiss, dispatch]);
if (!year || !state || state === 'ineligible') {
if (!year || wasDismissed || !state || state === 'ineligible') {
return null;
}
return (
<AnnualReportAnnouncement
year={year.toString()}
hasData={state === 'available'}
isLoading={state === 'generating'}
state={state}
onRequestBuild={handleBuildRequest}
onOpen={handleOpen}
onDismiss={dismiss}
/>
);
};

View File

@@ -1,26 +1,34 @@
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
export const CriticalUpdateBanner = () => (
<div className='warning-banner'>
<div className='warning-banner__message'>
<h1>
import { criticalUpdatesPending } from '@/flavours/glitch/initial_state';
export const CriticalUpdateBanner: FC = () => {
if (!criticalUpdatesPending) {
return null;
}
return (
<div className='warning-banner'>
<div className='warning-banner__message'>
<FormattedMessage
id='home.pending_critical_update.title'
defaultMessage='Critical security update available!'
tagName='h1'
/>
</h1>
<p>
<FormattedMessage
id='home.pending_critical_update.body'
defaultMessage='Please update your Mastodon server as soon as possible!'
/>{' '}
<a href='/admin/software_updates'>
<p>
<FormattedMessage
id='home.pending_critical_update.link'
defaultMessage='See updates'
/>
</a>
</p>
id='home.pending_critical_update.body'
defaultMessage='Please update your Mastodon server as soon as possible!'
/>{' '}
<a href='/admin/software_updates'>
<FormattedMessage
id='home.pending_critical_update.link'
defaultMessage='See updates'
/>
</a>
</p>
</div>
</div>
</div>
);
);
};

View File

@@ -15,7 +15,6 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { criticalUpdatesPending } from 'flavours/glitch/initial_state';
import { withBreakpoint } from 'flavours/glitch/features/ui/hooks/useBreakpoint';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@@ -27,6 +26,7 @@ import StatusListContainer from '../ui/containers/status_list_container';
import { ColumnSettings } from './components/column_settings';
import { CriticalUpdateBanner } from './components/critical_update_banner';
import { Announcements } from './components/announcements';
import { AnnualReportTimeline } from '../annual_report/timeline';
const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
@@ -129,7 +129,10 @@ class HomeTimeline extends PureComponent {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements, matchesBreakpoint } = this.props;
const pinned = !!columnId;
const { signedIn } = this.props.identity;
const banners = [];
const banners = [
<CriticalUpdateBanner key='critical-update-banner' />,
<AnnualReportTimeline key='annual-report' />
];
let announcementsButton;
@@ -147,10 +150,6 @@ class HomeTimeline extends PureComponent {
);
}
if (criticalUpdatesPending) {
banners.push(<CriticalUpdateBanner key='critical-update-banner' />);
}
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader