Add announcements and news in the homepage community tab (#4273)

* Also display "urgent" announcements at the top of the home page (which can be dismissed).
This commit is contained in:
Florian Rival
2022-09-09 10:50:49 +02:00
committed by GitHub
parent 5eb0aa9e14
commit 5cc999c0a3
16 changed files with 607 additions and 56 deletions

View File

@@ -0,0 +1,91 @@
// @flow
import * as React from 'react';
import {
type Announcement,
listAllAnnouncements,
} from '../Utils/GDevelopServices/Announcement';
type AnnouncementsFeedState = {|
announcements: ?(Announcement[]),
error: ?Error,
fetchAnnouncements: () => void,
|};
export const AnnouncementsFeedContext = React.createContext<AnnouncementsFeedState>(
{
announcements: null,
error: null,
fetchAnnouncements: () => {},
}
);
type AnnouncementsFeedStateProviderProps = {|
children: React.Node,
|};
export const AnnouncementsFeedStateProvider = ({
children,
}: AnnouncementsFeedStateProviderProps) => {
const [announcements, setAnnouncements] = React.useState<?(Announcement[])>(
null
);
const [error, setError] = React.useState<?Error>(null);
const isLoading = React.useRef<boolean>(false);
const fetchAnnouncements = React.useCallback(
() => {
if (isLoading.current) return;
(async () => {
setError(null);
isLoading.current = true;
try {
const announcements = await listAllAnnouncements();
setAnnouncements(announcements);
} catch (error) {
console.error(
`Unable to load the announcements from the api:`,
error
);
setError(error);
}
isLoading.current = false;
})();
},
[isLoading]
);
// Preload the assets and filters when the app loads.
React.useEffect(
() => {
// Don't attempt to load again assets and filters if they
// were loaded already.
if (announcements || isLoading.current) return;
const timeoutId = setTimeout(() => {
console.info('Pre-fetching announcements from the api...');
fetchAnnouncements();
}, 1000);
return () => clearTimeout(timeoutId);
},
[fetchAnnouncements, announcements, isLoading]
);
const announcementsFeedState = React.useMemo(
() => ({
announcements,
error,
fetchAnnouncements,
}),
[announcements, error, fetchAnnouncements]
);
return (
<AnnouncementsFeedContext.Provider value={announcementsFeedState}>
{children}
</AnnouncementsFeedContext.Provider>
);
};

View File

@@ -0,0 +1,100 @@
// @flow
import { Trans } from '@lingui/macro';
import { I18n } from '@lingui/react';
import * as React from 'react';
import AlertMessage from '../UI/AlertMessage';
import { ColumnStackLayout } from '../UI/Layout';
import PlaceholderError from '../UI/PlaceholderError';
import PlaceholderLoader from '../UI/PlaceholderLoader';
import RaisedButton from '../UI/RaisedButton';
import { selectMessageByLocale } from '../Utils/i18n/MessageByLocale';
import Window from '../Utils/Window';
import { AnnouncementsFeedContext } from './AnnouncementsFeedContext';
import Text from '../UI/Text';
import { Line } from '../UI/Grid';
import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
type AnnouncementsFeedProps = {|
level?: 'urgent' | 'normal',
canClose?: boolean,
addMargins?: boolean,
|};
export const AnnouncementsFeed = ({
level,
canClose,
addMargins,
}: AnnouncementsFeedProps) => {
const { announcements, error, fetchAnnouncements } = React.useContext(
AnnouncementsFeedContext
);
const { values, showAnnouncement } = React.useContext(PreferencesContext);
if (error) {
return (
<PlaceholderError onRetry={fetchAnnouncements}>
<Trans>
Can't load the announcements. Verify your internet connection or try
again later.
</Trans>
</PlaceholderError>
);
} else if (!announcements) {
return <PlaceholderLoader />;
}
const properLevelAnnouncements = level
? announcements.filter(announcement => announcement.level === level)
: announcements;
const displayedAnnouncements = canClose
? properLevelAnnouncements.filter(announcement => {
return !values.hiddenAnnouncements[announcement.id];
})
: properLevelAnnouncements;
if (!displayedAnnouncements.length) return null;
return (
<I18n>
{({ i18n }) => (
<Line noMargin={!addMargins}>
<ColumnStackLayout noMargin={!addMargins} expand>
{displayedAnnouncements.map(announcement => (
<AlertMessage
kind={announcement.type === 'warning' ? 'warning' : 'info'}
renderRightButton={() => (
<RaisedButton
label={selectMessageByLocale(
i18n,
announcement.buttonLabelByLocale
)}
onClick={() =>
Window.openExternalURL(announcement.buttonUrl)
}
/>
)}
onHide={
canClose
? () => {
showAnnouncement(announcement.id, false);
}
: undefined
}
>
<Text size="block-title">
<Trans>
{selectMessageByLocale(i18n, announcement.titleByLocale)}
</Trans>
</Text>
<Text>
{selectMessageByLocale(i18n, announcement.messageByLocale)}
</Text>
</AlertMessage>
))}
</ColumnStackLayout>
</Line>
)}
</I18n>
);
};

View File

@@ -14,6 +14,7 @@ import TikTok from '../../../UI/CustomSvgIcons/TikTok';
import SectionContainer, { SectionRow } from './SectionContainer';
import { ListItem } from '../../../UI/List';
import List from '@material-ui/core/List';
import { AnnouncementsFeed } from '../../../AnnouncementsFeed';
const styles = {
list: {
@@ -65,7 +66,11 @@ const CommunitySection = () => {
return (
<SectionContainer title={<Trans>Community</Trans>}>
<SectionRow>
<ColumnStackLayout alignItems="start" noMargin expand>
<ColumnStackLayout noMargin expand>
<Text size="title">
<Trans>News and announcements</Trans>
</Text>
<AnnouncementsFeed canClose={false} />
<Text size="title">
<Trans>Join the conversation</Trans>
</Text>

View File

@@ -123,7 +123,10 @@ export const HomePageMenu = ({
return (
<>
<Paper
style={windowWidth === 'large' ? styles.desktopMenu : styles.mobileMenu}
style={{
...(windowWidth === 'large' ? styles.desktopMenu : styles.mobileMenu),
borderRight: `1px solid ${GDevelopTheme.home.separator.color}`,
}}
square
>
<Column expand>

View File

@@ -4,7 +4,6 @@ import { Column, Line } from '../../../UI/Grid';
import Paper from '@material-ui/core/Paper';
import { useResponsiveWindowWidth } from '../../../UI/Reponsive/ResponsiveWindowMeasurer';
import Text from '../../../UI/Text';
import GDevelopThemeContext from '../../../UI/Theme/ThemeContext';
import ArrowLeft from '../../../UI/CustomSvgIcons/ArrowLeft';
import TextButton from '../../../UI/TextButton';
import { Trans } from '@lingui/macro';
@@ -57,7 +56,7 @@ const SectionContainer = ({
renderFooter,
}: Props) => {
const windowWidth = useResponsiveWindowWidth();
const GDevelopTheme = React.useContext(GDevelopThemeContext);
return (
<Column useFullHeight noMargin expand>
<Paper
@@ -65,7 +64,6 @@ const SectionContainer = ({
style={{
...styles.scrollContainer,
display: flexBody ? 'flex' : 'block',
borderLeft: `1px solid ${GDevelopTheme.home.separator.color}`,
...(windowWidth === 'small'
? styles.mobileScrollContainer
: styles.desktopScrollContainer),
@@ -100,12 +98,9 @@ const SectionContainer = ({
{renderFooter && (
<Paper
elevation={0}
style={{
borderLeft: `1px solid ${GDevelopTheme.home.separator.color}`,
...(windowWidth === 'small'
? styles.mobileFooter
: styles.desktopFooter),
}}
style={
windowWidth === 'small' ? styles.mobileFooter : styles.desktopFooter
}
square
>
{renderFooter()}

View File

@@ -1,6 +1,7 @@
// @flow
import * as React from 'react';
import { I18n } from '@lingui/react';
import Paper from '@material-ui/core/Paper';
import { Line, Column } from '../../../UI/Grid';
import { type RenderEditorContainerPropsWithRef } from '../BaseEditor';
import {
@@ -20,6 +21,8 @@ import { HomePageMenu, type HomeTab } from './HomePageMenu';
import PreferencesContext from '../../Preferences/PreferencesContext';
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
import { type ExampleShortHeader } from '../../../Utils/GDevelopServices/Example';
import { AnnouncementsFeed } from '../../../AnnouncementsFeed';
import { AnnouncementsFeedContext } from '../../../AnnouncementsFeed/AnnouncementsFeedContext';
type Props = {|
project: ?gdProject,
@@ -84,6 +87,7 @@ export const HomePage = React.memo<Props>(
AuthenticatedUserContext
);
const { fetchTutorials } = React.useContext(TutorialContext);
const { announcements } = React.useContext(AnnouncementsFeedContext);
const { fetchShowcasedGamesAndFilters } = React.useContext(
GamesShowcaseContext
);
@@ -167,49 +171,56 @@ export const HomePage = React.memo<Props>(
onOpenPreferences={onOpenPreferences}
onOpenAbout={onOpenAbout}
/>
{activeTab === 'get-started' && (
<GetStartedSection
onTabChange={setActiveTab}
onCreateProject={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onOpenOnboardingDialog={onOpenOnboardingDialog}
showGetStartedSection={showGetStartedSection}
setShowGetStartedSection={setShowGetStartedSection}
/>
)}
{activeTab === 'build' && (
<BuildSection
ref={buildSectionRef}
project={project}
canOpen={canOpen}
onChooseProject={onChooseProject}
onOpenProjectPreCreationDialog={
onOpenProjectPreCreationDialog
}
onShowAllExamples={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onSelectExample={exampleShortHeader =>
onCreateProject(exampleShortHeader)
}
onOpenRecentFile={onOpenRecentFile}
onChangeSubscription={onChangeSubscription}
storageProviders={storageProviders}
/>
)}
{activeTab === 'learn' && (
<LearnSection
onOpenOnboardingDialog={onOpenOnboardingDialog}
onCreateProject={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onTabChange={setActiveTab}
onOpenHelpFinder={onOpenHelpFinder}
/>
)}
{activeTab === 'play' && <PlaySection />}
{activeTab === 'community' && <CommunitySection />}
<Column noMargin expand>
{activeTab !== 'community' && !!announcements && (
<Paper elevation={0} style={{}} square>
<AnnouncementsFeed level="urgent" canClose addMargins />
</Paper>
)}
{activeTab === 'get-started' && (
<GetStartedSection
onTabChange={setActiveTab}
onCreateProject={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onOpenOnboardingDialog={onOpenOnboardingDialog}
showGetStartedSection={showGetStartedSection}
setShowGetStartedSection={setShowGetStartedSection}
/>
)}
{activeTab === 'build' && (
<BuildSection
ref={buildSectionRef}
project={project}
canOpen={canOpen}
onChooseProject={onChooseProject}
onOpenProjectPreCreationDialog={
onOpenProjectPreCreationDialog
}
onShowAllExamples={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onSelectExample={exampleShortHeader =>
onCreateProject(exampleShortHeader)
}
onOpenRecentFile={onOpenRecentFile}
onChangeSubscription={onChangeSubscription}
storageProviders={storageProviders}
/>
)}
{activeTab === 'learn' && (
<LearnSection
onOpenOnboardingDialog={onOpenOnboardingDialog}
onCreateProject={() =>
onCreateProject(/*exampleShortHeader=*/ null)
}
onTabChange={setActiveTab}
onOpenHelpFinder={onOpenHelpFinder}
/>
)}
{activeTab === 'play' && <PlaySection />}
{activeTab === 'community' && <CommunitySection />}
</Column>
</Line>
</Column>
</>

View File

@@ -182,6 +182,7 @@ export type PreferencesValues = {|
codeEditorThemeName: string,
hiddenAlertMessages: { [AlertMessageIdentifier]: boolean },
hiddenTutorialHints: { [string]: boolean },
hiddenAnnouncements: { [string]: boolean },
autoDisplayChangelog: boolean,
lastLaunchedVersion: ?string,
eventsSheetShowObjectThumbnails: boolean,
@@ -222,6 +223,8 @@ export type Preferences = {|
showAllAlertMessages: () => void,
showTutorialHint: (identifier: string, show: boolean) => void,
showAllTutorialHints: () => void,
showAnnouncement: (identifier: string, show: boolean) => void,
showAllAnnouncements: () => void,
verifyIfIsNewVersion: () => boolean,
setEventsSheetShowObjectThumbnails: (enabled: boolean) => void,
setAutosaveOnPreview: (enabled: boolean) => void,
@@ -280,6 +283,7 @@ export const initialPreferences = {
codeEditorThemeName: 'vs-dark',
hiddenAlertMessages: {},
hiddenTutorialHints: {},
hiddenAnnouncements: {},
autoDisplayChangelog: true,
lastLaunchedVersion: undefined,
eventsSheetShowObjectThumbnails: true,
@@ -314,6 +318,8 @@ export const initialPreferences = {
showAllAlertMessages: () => {},
showTutorialHint: (identifier: string, show: boolean) => {},
showAllTutorialHints: () => {},
showAnnouncement: (identifier: string, show: boolean) => {},
showAllAnnouncements: () => {},
verifyIfIsNewVersion: () => false,
setEventsSheetShowObjectThumbnails: () => {},
setAutosaveOnPreview: () => {},

View File

@@ -40,6 +40,7 @@ const PreferencesDialog = ({ i18n, onClose }: Props) => {
setAutoDownloadUpdates,
showAllAlertMessages,
showAllTutorialHints,
showAllAnnouncements,
setAutoDisplayChangelog,
setEventsSheetShowObjectThumbnails,
setAutosaveOnPreview,
@@ -290,6 +291,13 @@ const PreferencesDialog = ({ i18n, onClose }: Props) => {
disabled={!Object.keys(values.hiddenTutorialHints).length}
/>
</Line>
<Line>
<RaisedButton
label={<Trans>Reset hidden announcements</Trans>}
onClick={() => showAllAnnouncements()}
disabled={!Object.keys(values.hiddenAnnouncements).length}
/>
</Line>
</Column>
<Text size="block-title">
<Trans>Advanced</Trans>

View File

@@ -99,6 +99,8 @@ export default class PreferencesProvider extends React.Component<Props, State> {
showAllAlertMessages: this._showAllAlertMessages.bind(this),
showTutorialHint: this._showTutorialHint.bind(this),
showAllTutorialHints: this._showAllTutorialHints.bind(this),
showAnnouncement: this._showAnnouncement.bind(this),
showAllAnnouncements: this._showAllAnnouncements.bind(this),
verifyIfIsNewVersion: this._verifyIfIsNewVersion.bind(this),
setEventsSheetShowObjectThumbnails: this._setEventsSheetShowObjectThumbnails.bind(
this
@@ -447,6 +449,33 @@ export default class PreferencesProvider extends React.Component<Props, State> {
);
}
_showAnnouncement(identifier: string, show: boolean) {
this.setState(
state => ({
values: {
...state.values,
hiddenAnnouncements: {
...state.values.hiddenAnnouncements,
[identifier]: !show,
},
},
}),
() => this._persistValuesToLocalStorage(this.state)
);
}
_showAllAnnouncements() {
this.setState(
state => ({
values: {
...state.values,
hiddenAnnouncements: {},
},
}),
() => this._persistValuesToLocalStorage(this.state)
);
}
_persistValuesToLocalStorage(preferences: Preferences) {
try {
localStorage.setItem(

View File

@@ -33,6 +33,7 @@ import { ExtensionStoreStateProvider } from '../AssetStore/ExtensionStore/Extens
import { GamesShowcaseStateProvider } from '../GamesShowcase/GamesShowcaseContext';
import { TutorialStateProvider } from '../Tutorial/TutorialContext';
import ConfirmProvider from '../UI/Confirm/ConfirmProvider';
import { AnnouncementsFeedStateProvider } from '../AnnouncementsFeed/AnnouncementsFeedContext';
// Add the rtl plugin to the JSS instance to support RTL languages in material-ui components.
const jss = create({
@@ -105,7 +106,9 @@ export default class Providers extends React.Component<Props, {||}> {
<ExtensionStoreStateProvider>
<GamesShowcaseStateProvider>
<TutorialStateProvider>
{children({ i18n })}
<AnnouncementsFeedStateProvider>
{children({ i18n })}
</AnnouncementsFeedStateProvider>
</TutorialStateProvider>
</GamesShowcaseStateProvider>
</ExtensionStoreStateProvider>

View File

@@ -22,7 +22,7 @@ const styles = {
type Props = {|
kind: 'info' | 'warning' | 'error',
children: React.Node,
onHide?: () => void,
onHide?: ?() => void,
renderLeftIcon?: () => React.Node,
renderRightButton?: () => React.Node,
|};

View File

@@ -0,0 +1,21 @@
// @flow
import axios from 'axios';
import { GDevelopReleaseApi } from './ApiConfigs';
import { type MessageByLocale } from '../i18n/MessageByLocale';
export type Announcement = {
id: string,
titleByLocale: MessageByLocale,
messageByLocale: MessageByLocale,
type: 'info' | 'warning',
level: 'normal' | 'urgent',
buttonUrl: string,
buttonLabelByLocale: MessageByLocale,
};
export const listAllAnnouncements = async (): Promise<Array<Announcement>> => {
const response = await axios.get(
`${GDevelopReleaseApi.baseUrl}/announcement`
);
return response.data;
};

View File

@@ -0,0 +1,27 @@
// @flow
import { type I18n as I18nType } from '@lingui/core';
export type MessageByLocale = { [string]: string };
export const selectMessageByLocale = (
i18n: I18nType,
messageByLocale: MessageByLocale
): string => {
if (!messageByLocale) return '';
if (typeof messageByLocale === 'string') return messageByLocale;
if (typeof messageByLocale !== 'object') return '';
const language = i18n.language;
if (messageByLocale[language]) return messageByLocale[language];
const languageFirstCode = language.split('-')[0];
if (messageByLocale[languageFirstCode])
return messageByLocale[languageFirstCode];
if (messageByLocale['en']) return messageByLocale['en'];
const firstLanguage = Object.keys(messageByLocale)[0];
if (messageByLocale[firstLanguage]) return messageByLocale[firstLanguage];
return '';
};

View File

@@ -0,0 +1,110 @@
// @flow
import { type I18n as I18nType } from '@lingui/core';
import { selectMessageByLocale } from './MessageByLocale';
// $FlowExpectedError
const makeFakeI18n = (fakeI18n): I18nType => fakeI18n;
describe('MessageByLocale', () => {
describe('selectMessageByLocale', () => {
test('select the proper message according to the language', () => {
expect(
selectMessageByLocale(makeFakeI18n({ language: 'en' }), {
en: 'Test',
})
).toBe('Test');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'en' }), {
en: 'Test',
fr: 'Test2',
})
).toBe('Test');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), {
en: 'Test',
fr: 'Test2',
})
).toBe('Test2');
});
test('fallback to the same language even if not fully qualifying for the region', () => {
expect(
selectMessageByLocale(makeFakeI18n({ language: 'pt-br' }), {
'pt-pt': 'Message 1',
'pt-br': 'Message 2',
})
).toBe('Message 2');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'pt-OTHER' }), {
'pt-pt': 'Message 1',
pt: 'Message 2',
})
).toBe('Message 2');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'pt-pt' }), {
'pt-pt': 'Message 1',
pt: 'Message 1',
})
).toBe('Message 1');
});
test('fallback to english or the only language available', () => {
expect(
selectMessageByLocale(makeFakeI18n({ language: 'en' }), {
fr: 'Only this is available.',
})
).toBe('Only this is available.');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'pt-BR' }), {
en: 'Test',
fr: 'Test2',
})
).toBe('Test');
expect(
selectMessageByLocale(makeFakeI18n({ language: 'pt-OTHER' }), {
'pt-pt': 'Message 1',
'pt-br': 'Message 2',
en: 'Message 3',
})
).toBe('Message 3');
expect(selectMessageByLocale(makeFakeI18n({ language: 'fr' }), {})).toBe(
''
);
});
test('handles type errors gracefully', () => {
expect(
// $FlowExpectedError
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), 'Test')
).toBe('Test');
// $FlowExpectedError
expect(selectMessageByLocale(makeFakeI18n({ language: 'fr' }), 0)).toBe(
''
);
// $FlowExpectedError
expect(selectMessageByLocale(makeFakeI18n({ language: 'fr' }), 123)).toBe(
''
);
// $FlowExpectedError
expect(selectMessageByLocale(makeFakeI18n({ language: 'fr' }), [])).toBe(
''
);
expect(
// $FlowExpectedError
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), null)
).toBe('');
expect(
// $FlowExpectedError
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), undefined)
).toBe('');
expect(
// $FlowExpectedError
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), false)
).toBe('');
expect(
// $FlowExpectedError
selectMessageByLocale(makeFakeI18n({ language: 'fr' }), true)
).toBe('');
});
});
});

View File

@@ -27,6 +27,7 @@ import {
} from '../../Utils/GDevelopServices/Asset';
import { formatISO, subDays } from 'date-fns';
import { type Comment } from '../../Utils/GDevelopServices/Play';
import { type Announcement } from '../../Utils/GDevelopServices/Announcement';
export const indieFirebaseUser: FirebaseUser = {
uid: 'indie-user',
@@ -1279,3 +1280,58 @@ export const commentProcessed: Comment = {
updatedAt: 1515084393000,
processedAt: 1515084393000,
};
export const fakeAnnouncements: Announcement[] = [
{
id: '123',
type: 'info',
level: 'normal',
titleByLocale: {
en: 'Some title',
},
messageByLocale: {
en: 'Something to announce which is really really cool',
},
buttonLabelByLocale: { en: 'View' },
buttonUrl: 'https://gdevelop.io',
},
{
id: '124',
type: 'info',
level: 'urgent',
titleByLocale: {
en: 'Some title',
},
messageByLocale: {
en: 'Something nothing important but urgent to announce.',
},
buttonLabelByLocale: { en: 'View' },
buttonUrl: 'https://gdevelop.io',
},
{
id: '125',
type: 'warning',
level: 'urgent',
titleByLocale: {
en: 'Some title',
},
messageByLocale: {
en: 'Something important and urgent to announce.',
},
buttonLabelByLocale: { en: 'View' },
buttonUrl: 'https://gdevelop.io',
},
{
id: '126',
type: 'warning',
level: 'normal',
titleByLocale: {
en: 'Some title',
},
messageByLocale: {
en: 'Something important but not urgent to announce.',
},
buttonLabelByLocale: { en: 'View' },
buttonUrl: 'https://gdevelop.io',
},
];

View File

@@ -0,0 +1,86 @@
// @flow
import * as React from 'react';
import { action } from '@storybook/addon-actions';
import muiDecorator from '../../ThemeDecorator';
import paperDecorator from '../../PaperDecorator';
import { AnnouncementsFeed } from '../../../AnnouncementsFeed';
import { AnnouncementsFeedContext } from '../../../AnnouncementsFeed/AnnouncementsFeedContext';
import { fakeAnnouncements } from '../../../fixtures/GDevelopServicesTestData';
export default {
title: 'AnnouncementsFeed',
component: AnnouncementsFeed,
decorators: [paperDecorator, muiDecorator],
};
export const ErrorLoadingAnnouncements = () => {
return (
<AnnouncementsFeedContext.Provider
value={{
announcements: null,
error: new Error('Fake error'),
fetchAnnouncements: action('fetchAnnouncements'),
}}
>
<AnnouncementsFeed />
</AnnouncementsFeedContext.Provider>
);
};
export const LoadingAnnouncements = () => {
return (
<AnnouncementsFeedContext.Provider
value={{
announcements: null,
error: null,
fetchAnnouncements: action('fetchAnnouncements'),
}}
>
<AnnouncementsFeed />
</AnnouncementsFeedContext.Provider>
);
};
export const Default = () => {
return (
<AnnouncementsFeedContext.Provider
value={{
announcements: fakeAnnouncements,
error: null,
fetchAnnouncements: action('fetchAnnouncements'),
}}
>
<AnnouncementsFeed />
</AnnouncementsFeedContext.Provider>
);
};
export const DefaultWithMargins = () => {
return (
<AnnouncementsFeedContext.Provider
value={{
announcements: fakeAnnouncements,
error: null,
fetchAnnouncements: action('fetchAnnouncements'),
}}
>
<AnnouncementsFeed addMargins />
</AnnouncementsFeedContext.Provider>
);
};
export const OnlyUrgent = () => {
return (
<AnnouncementsFeedContext.Provider
value={{
announcements: fakeAnnouncements,
error: null,
fetchAnnouncements: action('fetchAnnouncements'),
}}
>
<AnnouncementsFeed level="urgent" />
</AnnouncementsFeedContext.Provider>
);
};