pl-fe: move files around

Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
Nicole Mikołajczyk
2025-05-16 20:11:27 +02:00
parent f29850623f
commit 31019121cb
18 changed files with 510 additions and 563 deletions

View File

@ -1,110 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { useParams } from 'react-router-dom';
import { useAccountLookup } from 'pl-fe/api/hooks/accounts/use-account-lookup';
import LoadMore from 'pl-fe/components/load-more';
import MissingIndicator from 'pl-fe/components/missing-indicator';
import Column from 'pl-fe/components/ui/column';
import Spinner from 'pl-fe/components/ui/spinner';
import { type AccountGalleryAttachment, useAccountGallery } from 'pl-fe/hooks/use-account-gallery';
import { useModalsStore } from 'pl-fe/stores/modals';
import MediaItem from './components/media-item';
const AccountGallery = () => {
const { username } = useParams<{ username: string }>();
const { openModal } = useModalsStore();
const {
account,
isLoading: accountLoading,
isUnavailable,
} = useAccountLookup(username, { withRelationship: true });
const { data: attachments, isFetching, isLoading, hasNextPage: hasMore, fetchNextPage } = useAccountGallery(account?.id!);
const handleScrollToBottom = () => {
if (hasMore) {
handleLoadMore();
}
};
const handleLoadMore = () => {
fetchNextPage({ cancelRefetch: false });
};
const handleLoadOlder: React.MouseEventHandler = e => {
e.preventDefault();
handleScrollToBottom();
};
const handleOpenMedia = (attachment: AccountGalleryAttachment) => {
if (attachment.type === 'video') {
openModal('VIDEO', { media: attachment, statusId: attachment.status_id });
} else {
openModal('MEDIA', { index: attachment.index, statusId: attachment.status_id });
}
};
if (accountLoading || isLoading) {
return (
<Column>
<Spinner />
</Column>
);
}
if (!account) {
return (
<MissingIndicator />
);
}
let loadOlder = null;
if (hasMore && !(isFetching && attachments.length === 0)) {
loadOlder = <LoadMore className='my-auto mt-4' visible={!isFetching} onClick={handleLoadOlder} />;
}
if (isUnavailable) {
return (
<Column>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
</Column>
);
}
return (
<Column label={`@${account.acct}`} transparent withHeader={false}>
<div role='feed' className='grid grid-cols-2 gap-1 overflow-hidden rounded-md sm:grid-cols-3'>
{attachments.map((attachment, index) => (
<MediaItem
key={`${attachment.status_id}+${attachment.id}`}
attachment={attachment}
onOpenMedia={handleOpenMedia}
isLast={index === attachments.length - 1}
/>
))}
{!isLoading && attachments.length === 0 && (
<div className='empty-column-indicator col-span-2 sm:col-span-3'>
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
</div>
)}
</div>
{loadOlder}
{isFetching && attachments.length === 0 && (
<div className='relative flex-auto px-8 py-4'>
<Spinner />
</div>
)}
</Column>
);
};
export { AccountGallery as default };

View File

@ -1,43 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import Account from 'pl-fe/components/account';
import Icon from 'pl-fe/components/icon';
import HStack from 'pl-fe/components/ui/hstack';
import Text from 'pl-fe/components/ui/text';
import Emojify from 'pl-fe/features/emoji/emojify';
import type { Account as AccountEntity } from 'pl-fe/normalizers/account';
interface IMovedNote {
from: AccountEntity;
to: AccountEntity;
}
const MovedNote: React.FC<IMovedNote> = ({ from, to }) => (
<div className='p-4'>
<HStack className='mb-2' alignItems='center' space={1.5}>
<Icon
src={require('@tabler/icons/outline/briefcase.svg')}
className='flex-none text-primary-600 dark:text-primary-400'
/>
<div className='truncate'>
<Text theme='muted' size='sm' truncate>
<FormattedMessage
id='notification.move'
defaultMessage='{name} moved to {targetName}'
values={{
name: <span><Emojify text={from.display_name} emojis={from.emojis} /></span>,
targetName: to.acct,
}}
/>
</Text>
</div>
</HStack>
<Account account={to} withRelationship={false} />
</div>
);
export { MovedNote as default };

View File

@ -13,6 +13,7 @@ import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
import AltIndicator from 'pl-fe/components/alt-indicator';
import Badge from 'pl-fe/components/badge';
import DropdownMenu, { Menu } from 'pl-fe/components/dropdown-menu';
import Icon from 'pl-fe/components/icon';
import StillImage from 'pl-fe/components/still-image';
import Avatar from 'pl-fe/components/ui/avatar';
import HStack from 'pl-fe/components/ui/hstack';
@ -21,7 +22,7 @@ import Popover from 'pl-fe/components/ui/popover';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import MovedNote from 'pl-fe/features/account-timeline/components/moved-note';
import Emojify from 'pl-fe/features/emoji/emojify';
import ActionButton from 'pl-fe/features/ui/components/action-button';
import SubscriptionButton from 'pl-fe/features/ui/components/subscription-button';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
@ -89,6 +90,37 @@ const messages = defineMessages({
loadActivitiesFail: { id: 'account.load_activities.fail', defaultMessage: 'Failed to fetch latest posts' },
});
interface IMovedNote {
from: AccountEntity;
to: AccountEntity;
}
const MovedNote: React.FC<IMovedNote> = ({ from, to }) => (
<div className='p-4'>
<HStack className='mb-2' alignItems='center' space={1.5}>
<Icon
src={require('@tabler/icons/outline/briefcase.svg')}
className='flex-none text-primary-600 dark:text-primary-400'
/>
<div className='truncate'>
<Text theme='muted' size='sm' truncate>
<FormattedMessage
id='notification.move'
defaultMessage='{name} moved to {targetName}'
values={{
name: <span><Emojify text={from.display_name} emojis={from.emojis} /></span>,
targetName: to.acct,
}}
/>
</Text>
</div>
</HStack>
<Account account={to} withRelationship={false} />
</div>
);
interface IHeader {
account?: Account;
}

View File

@ -1,39 +0,0 @@
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import Hashtag from 'pl-fe/components/hashtag';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
import PlaceholderHashtag from 'pl-fe/features/placeholder/components/placeholder-hashtag';
import { useFollowedTags } from 'pl-fe/queries/hashtags/use-followed-tags';
const messages = defineMessages({
heading: { id: 'column.followed_tags', defaultMessage: 'Followed hashtags' },
});
const FollowedTags = () => {
const intl = useIntl();
const { data: tags = [], isLoading, hasNextPage, fetchNextPage } = useFollowedTags();
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage="You haven't followed any hashtag yet." />;
return (
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='followedTags'
emptyMessage={emptyMessage}
isLoading={isLoading}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
placeholderComponent={PlaceholderHashtag}
placeholderCount={5}
itemClassName='pb-3'
>
{tags.map(tag => <Hashtag key={tag.name} hashtag={tag} />)}
</ScrollableList>
</Column>
);
};
export { FollowedTags as default };

View File

@ -1,103 +0,0 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { type FilterType, setFilter } from 'pl-fe/actions/notifications';
import Icon from 'pl-fe/components/ui/icon';
import Tabs from 'pl-fe/components/ui/tabs';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useSettings } from 'pl-fe/hooks/use-settings';
import type { Item } from 'pl-fe/components/ui/tabs';
const messages = defineMessages({
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
events: { id: 'notifications.filter.events', defaultMessage: 'Events' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
});
const NotificationFilterBar = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const settings = useSettings();
const features = useFeatures();
const selectedFilter = settings.notifications.quickFilter.active;
const advancedMode = settings.notifications.quickFilter.advanced;
const onClick = (notificationType: FilterType) => () => {
try {
dispatch(setFilter(notificationType, true));
} catch (e) {
console.error(e);
}
};
const items: Item[] = [
{
text: intl.formatMessage(messages.all),
action: onClick('all'),
name: 'all',
},
];
if (!advancedMode) {
items.push({
text: intl.formatMessage(messages.mentions),
action: onClick('mention'),
name: 'mention',
});
} else {
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/at.svg')} />,
title: intl.formatMessage(messages.mentions),
action: onClick('mention'),
name: 'mention',
});
if (features.accountNotifies) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
title: intl.formatMessage(messages.statuses),
action: onClick('status'),
name: 'status',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/star.svg')} />,
title: intl.formatMessage(messages.favourites),
action: onClick('favourite'),
name: 'favourite',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/repeat.svg')} />,
title: intl.formatMessage(messages.boosts),
action: onClick('reblog'),
name: 'reblog',
});
if (features.polls) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/chart-bar.svg')} />,
title: intl.formatMessage(messages.polls),
action: onClick('poll'),
name: 'poll',
});
if (features.events) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/calendar.svg')} />,
title: intl.formatMessage(messages.events),
action: onClick('events'),
name: 'events',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
title: intl.formatMessage(messages.follows),
action: onClick('follow'),
name: 'follow',
});
}
return <Tabs items={items} activeItem={selectedFilter} />;
};
export { NotificationFilterBar as default };

View File

@ -1,95 +0,0 @@
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useSearchParams } from 'react-router-dom-v5-compat';
import Input from 'pl-fe/components/ui/input';
import SvgIcon from 'pl-fe/components/ui/svg-icon';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
});
const Search = () => {
const [params, setParams] = useSearchParams();
const [value, setValue] = useState(params.get('q') || '');
const intl = useIntl();
const setQuery = (value: string) => {
setParams(params => ({ ...Object.fromEntries(params.entries()), q: value }));
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
setValue(value);
};
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
if (params.get('q') === value) {
if (value.length > 0) {
setValue('');
setQuery('');
}
} else {
setQuery(value);
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
setQuery(value);
} else if (event.key === 'Escape') {
document.querySelector('.ui')?.parentElement?.focus();
}
};
return (
<div
className='sticky top-[76px] z-10 w-full bg-white/90 backdrop-blur black:bg-black/80 dark:bg-primary-900/90'
>
<label htmlFor='search' className='sr-only'>{intl.formatMessage(messages.placeholder)}</label>
<div className='relative'>
<Input
type='text'
id='search'
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
autoFocus
theme='search'
className='pr-10 rtl:pl-10 rtl:pr-3'
/>
<div
role='button'
tabIndex={0}
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto'
onClick={handleClick}
>
{params.get('q') === value ? (
<SvgIcon
src={require('@tabler/icons/outline/x.svg')}
className='size-4 text-gray-600'
aria-label={intl.formatMessage(messages.placeholder)}
/>
) : (
<SvgIcon
src={require('@tabler/icons/outline/search.svg')}
className='size-4 text-gray-600'
/>
)}
</div>
</div>
</div>
);
};
export { Search as default };

View File

@ -1,25 +0,0 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import Column from 'pl-fe/components/ui/column';
import Search from 'pl-fe/features/search/components/search';
import SearchResults from 'pl-fe/features/search/components/search-results';
const messages = defineMessages({
heading: { id: 'column.search', defaultMessage: 'Search' },
});
const SearchPage = () => {
const intl = useIntl();
return (
<Column label={intl.formatMessage(messages.heading)}>
<div className='space-y-4'>
<Search />
<SearchResults />
</div>
</Column>
);
};
export { SearchPage as default };

View File

@ -1,17 +1,22 @@
import { lazy } from 'react';
// Pages
export const AccountGallery = lazy(() => import('pl-fe/pages/accounts/account-gallery'));
export const AccountTimeline = lazy(() => import('pl-fe/pages/accounts/account-timeline'));
export const AboutPage = lazy(() => import('pl-fe/pages/utils/about'));
export const Aliases = lazy(() => import('pl-fe/pages/settings/aliases'));
export const Announcements = lazy(() => import('pl-fe/pages/dashboard/announcements'));
export const AuthTokenList = lazy(() => import('pl-fe/pages/settings/auth-token-list'));
export const Backups = lazy(() => import('pl-fe/pages/settings/backups'));
export const Blocks = lazy(() => import('pl-fe/pages/settings/blocks'));
export const BookmarkFolders = lazy(() => import('pl-fe/pages/status-lists/bookmark-folders'));
export const Bookmarks = lazy(() => import('pl-fe/pages/status-lists/bookmarks'));
export const BubbleTimeline = lazy(() => import('pl-fe/pages/timelines/bubble-timeline'));
export const ChatIndex = lazy(() => import('pl-fe/pages/chats/chats'));
export const Circle = lazy(() => import('pl-fe/pages/fun/circle'));
export const Circles = lazy(() => import('pl-fe/pages/account-lists/circles'));
export const CommunityTimeline = lazy(() => import('pl-fe/pages/timelines/community-timeline'));
export const ComposeEvent = lazy(() => import('pl-fe/pages/statuses/compose-event'));
export const Conversations = lazy(() => import('pl-fe/pages/status-lists/conversations'));
export const CreateApp = lazy(() => import('pl-fe/pages/developers/create-app'));
export const CryptoDonate = lazy(() => import('pl-fe/pages/utils/crypto-donate'));
@ -21,16 +26,21 @@ export const Developers = lazy(() => import('pl-fe/pages/developers/developers')
export const Directory = lazy(() => import('pl-fe/pages/account-lists/directory'));
export const DomainBlocks = lazy(() => import('pl-fe/pages/settings/domain-blocks'));
export const Domains = lazy(() => import('pl-fe/pages/dashboard/domains'));
export const DraftStatuses = lazy(() => import('pl-fe/pages/status-lists/draft-statuses'));
export const EditEmail = lazy(() => import('pl-fe/pages/settings/edit-email'));
export const EditFilter = lazy(() => import('pl-fe/pages/settings/edit-filter'));
export const EditGroup = lazy(() => import('pl-fe/pages/groups/edit-group'));
export const EditPassword = lazy(() => import('pl-fe/pages/settings/edit-password'));
export const EditProfile = lazy(() => import('pl-fe/pages/settings/edit-profile'));
export const EventDiscussion = lazy(() => import('pl-fe/pages/statuses/event-discussion'));
export const EventInformation = lazy(() => import('pl-fe/pages/statuses/event-information'));
export const Events = lazy(() => import('pl-fe/pages/status-lists/events'));
export const ExportData = lazy(() => import('pl-fe/pages/settings/export-data'));
export const ExternalLogin = lazy(() => import('pl-fe/pages/auth/external-login'));
export const FavouritedStatuses = lazy(() => import('pl-fe/pages/status-lists/favourited-statuses'));
export const FederationRestrictions = lazy(() => import('pl-fe/pages/utils/federation-restrictions'));
export const Filters = lazy(() => import('pl-fe/pages/settings'));
export const Filters = lazy(() => import('pl-fe/pages/settings/filters'));
export const FollowedTags = lazy(() => import('pl-fe/pages/settings'));
export const Followers = lazy(() => import('pl-fe/pages/account-lists/followers'));
export const Following = lazy(() => import('pl-fe/pages/account-lists/following'));
export const FollowRecommendations = lazy(() => import('pl-fe/pages/account-lists/follow-recommendations'));
@ -47,6 +57,7 @@ export const HomeTimeline = lazy(() => import('pl-fe/pages/timelines/home-timeli
export const ImportData = lazy(() => import('pl-fe/pages/settings/import-data'));
export const IntentionalError = lazy(() => import('pl-fe/pages/utils/intentional-error'));
export const InteractionPolicies = lazy(() => import('pl-fe/pages/settings/interaction-policies'));
export const InteractionRequests = lazy(() => import('pl-fe/pages/status-lists/interaction-requests'));
export const LandingPage = lazy(() => import('pl-fe/pages/utils/landing'));
export const LandingTimeline = lazy(() => import('pl-fe/pages/timelines/landing-timeline'));
export const LinkTimeline = lazy(() => import('pl-fe/pages/timelines/link-timeline'));
@ -60,6 +71,7 @@ export const Migration = lazy(() => import('pl-fe/pages/settings/migration'));
export const ModerationLog = lazy(() => import('pl-fe/pages/dashboard/moderation-log'));
export const Mutes = lazy(() => import('pl-fe/pages/settings/mutes'));
export const NewStatus = lazy(() => import('pl-fe/pages/utils/new-status'));
export const Notifications = lazy(() => import('pl-fe/pages/notifications/notifications'));
export const OutgoingFollowRequests = lazy(() => import('pl-fe/pages/account-lists/outgoing-follow-requests'));
export const PasswordReset = lazy(() => import('pl-fe/pages/auth/password-reset'));
export const PinnedStatuses = lazy(() => import('pl-fe/pages/status-lists/pinned-statuses'));
@ -71,34 +83,19 @@ export const RegistrationPage = lazy(() => import('pl-fe/pages/auth/registration
export const Relays = lazy(() => import('pl-fe/pages/dashboard/relays'));
export const RemoteTimeline = lazy(() => import('pl-fe/pages/timelines/remote-timeline'));
export const Rules = lazy(() => import('pl-fe/pages/dashboard/rules'));
export const ScheduledStatuses = lazy(() => import('pl-fe/pages/status-lists/scheduled-statuses'));
export const Search = lazy(() => import('pl-fe/pages/search/search'));
export const ServiceWorkerInfo = lazy(() => import('pl-fe/pages/developers/service-worker-info'));
export const ServerInfo = lazy(() => import('pl-fe/pages/utils/server-info'));
export const Settings = lazy(() => import('pl-fe/pages/settings/settings'));
export const SettingsStore = lazy(() => import('pl-fe/pages/developers/settings-store'));
export const Share = lazy(() => import('pl-fe/pages/utils/share'));
export const Status = lazy(() => import('pl-fe/pages/statuses/status'));
export const TestTimeline = lazy(() => import('pl-fe/pages/timelines/test-timeline'));
export const ThemeEditor = lazy(() => import('pl-fe/pages/dashboard/theme-editor'));
export const UrlPrivacy = lazy(() => import('pl-fe/pages/settings/url-privacy'));
export const UserIndex = lazy(() => import('pl-fe/pages/dashboard/user-index'));
export const DraftStatuses = lazy(() => import('pl-fe/pages/status-lists/draft-statuses'));
export const EventDiscussion = lazy(() => import('pl-fe/pages/statuses/event-discussion'));
export const EventInformation = lazy(() => import('pl-fe/pages/statuses/event-information'));
export const Events = lazy(() => import('pl-fe/pages/status-lists/events'));
export const InteractionRequests = lazy(() => import('pl-fe/pages/status-lists/interaction-requests'));
export const ScheduledStatuses = lazy(() => import('pl-fe/pages/status-lists/scheduled-statuses'));
export const Status = lazy(() => import('pl-fe/pages/statuses/status'));
export const AccountGallery = lazy(() => import('pl-fe/features/account-gallery'));
export const AccountTimeline = lazy(() => import('pl-fe/features/account-timeline'));
export const BookmarkFolders = lazy(() => import('pl-fe/features/bookmark-folders'));
export const Circle = lazy(() => import('pl-fe/features/circle'));
export const ComposeEditor = lazy(() => import('pl-fe/features/compose/editor'));
export const ComposeEvent = lazy(() => import('pl-fe/features/compose-event'));
export const FollowedTags = lazy(() => import('pl-fe/features/followed-tags'));
export const Notifications = lazy(() => import('pl-fe/features/notifications'));
export const Search = lazy(() => import('pl-fe/features/search'));
// Panels
export const AccountNotePanel = lazy(() => import('pl-fe/features/ui/components/panels/account-note-panel'));
export const AnnouncementsPanel = lazy(() => import('pl-fe/components/announcements/announcements-panel'));
@ -123,6 +120,7 @@ export const WhoToFollowPanel = lazy(() => import('pl-fe/features/ui/components/
export const Audio = lazy(() => import('pl-fe/features/audio'));
export const ChatWidget = lazy(() => import('pl-fe/features/chats/components/chat-widget/chat-widget'));
export const ComposeEditor = lazy(() => import('pl-fe/features/compose/editor'));
export const ComposeForm = lazy(() => import('pl-fe/features/compose/components/compose-form'));
export const CryptoAddress = lazy(() => import('pl-fe/features/crypto-donate/components/crypto-address'));
export const CryptoIcon = lazy(() => import('pl-fe/features/crypto-donate/components/crypto-icon'));

View File

@ -1,15 +1,21 @@
import clsx from 'clsx';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { Link, useParams } from 'react-router-dom';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
import { useAccountLookup } from 'pl-fe/api/hooks/accounts/use-account-lookup';
import Blurhash from 'pl-fe/components/blurhash';
import Icon from 'pl-fe/components/icon';
import LoadMore from 'pl-fe/components/load-more';
import MissingIndicator from 'pl-fe/components/missing-indicator';
import StillImage from 'pl-fe/components/still-image';
import Column from 'pl-fe/components/ui/column';
import Spinner from 'pl-fe/components/ui/spinner';
import { type AccountGalleryAttachment, useAccountGallery } from 'pl-fe/hooks/use-account-gallery';
import { useSettings } from 'pl-fe/hooks/use-settings';
import { isIOS } from 'pl-fe/is-mobile';
import type { AccountGalleryAttachment } from 'pl-fe/hooks/use-account-gallery';
import { useModalsStore } from 'pl-fe/stores/modals';
interface IMediaItem {
attachment: AccountGalleryAttachment;
@ -135,4 +141,99 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia, isLast }) =>
);
};
export { MediaItem as default };
const AccountGalleryPage = () => {
const { username } = useParams<{ username: string }>();
const { openModal } = useModalsStore();
const {
account,
isLoading: accountLoading,
isUnavailable,
} = useAccountLookup(username, { withRelationship: true });
const { data: attachments, isFetching, isLoading, hasNextPage: hasMore, fetchNextPage } = useAccountGallery(account?.id!);
const handleScrollToBottom = () => {
if (hasMore) {
handleLoadMore();
}
};
const handleLoadMore = () => {
fetchNextPage({ cancelRefetch: false });
};
const handleLoadOlder: React.MouseEventHandler = e => {
e.preventDefault();
handleScrollToBottom();
};
const handleOpenMedia = (attachment: AccountGalleryAttachment) => {
if (attachment.type === 'video') {
openModal('VIDEO', { media: attachment, statusId: attachment.status_id });
} else {
openModal('MEDIA', { index: attachment.index, statusId: attachment.status_id });
}
};
if (accountLoading || isLoading) {
return (
<Column>
<Spinner />
</Column>
);
}
if (!account) {
return (
<MissingIndicator />
);
}
let loadOlder = null;
if (hasMore && !(isFetching && attachments.length === 0)) {
loadOlder = <LoadMore className='my-auto mt-4' visible={!isFetching} onClick={handleLoadOlder} />;
}
if (isUnavailable) {
return (
<Column>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
</Column>
);
}
return (
<Column label={`@${account.acct}`} transparent withHeader={false}>
<div role='feed' className='grid grid-cols-2 gap-1 overflow-hidden rounded-md sm:grid-cols-3'>
{attachments.map((attachment, index) => (
<MediaItem
key={`${attachment.status_id}+${attachment.id}`}
attachment={attachment}
onOpenMedia={handleOpenMedia}
isLast={index === attachments.length - 1}
/>
))}
{!isLoading && attachments.length === 0 && (
<div className='empty-column-indicator col-span-2 sm:col-span-3'>
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
</div>
)}
</div>
{loadOlder}
{isFetching && attachments.length === 0 && (
<div className='relative flex-auto px-8 py-4'>
<Spinner />
</div>
)}
</Column>
);
};
export { AccountGalleryPage as default };

View File

@ -18,14 +18,14 @@ import { makeGetStatusIds } from 'pl-fe/selectors';
const getStatusIds = makeGetStatusIds();
interface IAccountTimeline {
interface IAccountTimelinePage {
params: {
username: string;
};
withReplies?: boolean;
}
const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = false }) => {
const AccountTimelinePage: React.FC<IAccountTimelinePage> = ({ params, withReplies = false }) => {
const history = useHistory();
const dispatch = useAppDispatch();
const features = useFeatures();
@ -103,4 +103,4 @@ const AccountTimeline: React.FC<IAccountTimeline> = ({ params, withReplies = fal
);
};
export { AccountTimeline as default };
export { AccountTimelinePage as default };

View File

@ -36,7 +36,7 @@ const messages = defineMessages({
done: { id: 'interactions_circle.state.done', defaultMessage: 'Finalizing…' },
});
const Circle: React.FC = () => {
const CirclePage: React.FC = () => {
const [{ state, progress }, setProgress] = useState<{
state: 'unrequested' | 'pending' | 'fetchingStatuses' | 'fetchingFavourites' | 'fetchingAvatars' | 'drawing' | 'done';
progress: number;
@ -220,4 +220,4 @@ const Circle: React.FC = () => {
);
};
export { Circle as default };
export { CirclePage as default };

View File

@ -5,32 +5,123 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { createSelector } from 'reselect';
import {
type FilterType,
expandNotifications,
markReadNotifications,
scrollTopNotifications,
setFilter,
} from 'pl-fe/actions/notifications';
import PullToRefresh from 'pl-fe/components/pull-to-refresh';
import ScrollTopButton from 'pl-fe/components/scroll-top-button';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
import Icon from 'pl-fe/components/ui/icon';
import Portal from 'pl-fe/components/ui/portal';
import Tabs from 'pl-fe/components/ui/tabs';
import PlaceholderNotification from 'pl-fe/features/placeholder/components/placeholder-notification';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useSettings } from 'pl-fe/hooks/use-settings';
import FilterBar from './components/filter-bar';
import Notification from './components/notification';
import Notification from '../../features/notifications/components/notification';
import type { Item } from 'pl-fe/components/ui/tabs';
import type { RootState } from 'pl-fe/store';
import type { VirtuosoHandle } from 'react-virtuoso';
const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
queue: { id: 'notifications.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {notification} other {notifications}}' },
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
events: { id: 'notifications.filter.events', defaultMessage: 'Events' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
});
const FilterBar = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const settings = useSettings();
const features = useFeatures();
const selectedFilter = settings.notifications.quickFilter.active;
const advancedMode = settings.notifications.quickFilter.advanced;
const onClick = (notificationType: FilterType) => () => {
try {
dispatch(setFilter(notificationType, true));
} catch (e) {
console.error(e);
}
};
const items: Item[] = [
{
text: intl.formatMessage(messages.all),
action: onClick('all'),
name: 'all',
},
];
if (!advancedMode) {
items.push({
text: intl.formatMessage(messages.mentions),
action: onClick('mention'),
name: 'mention',
});
} else {
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/at.svg')} />,
title: intl.formatMessage(messages.mentions),
action: onClick('mention'),
name: 'mention',
});
if (features.accountNotifies) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
title: intl.formatMessage(messages.statuses),
action: onClick('status'),
name: 'status',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/star.svg')} />,
title: intl.formatMessage(messages.favourites),
action: onClick('favourite'),
name: 'favourite',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/repeat.svg')} />,
title: intl.formatMessage(messages.boosts),
action: onClick('reblog'),
name: 'reblog',
});
if (features.polls) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/chart-bar.svg')} />,
title: intl.formatMessage(messages.polls),
action: onClick('poll'),
name: 'poll',
});
if (features.events) items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/calendar.svg')} />,
title: intl.formatMessage(messages.events),
action: onClick('events'),
name: 'events',
});
items.push({
text: <Icon className='size-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
title: intl.formatMessage(messages.follows),
action: onClick('follow'),
name: 'follow',
});
}
return <Tabs items={items} activeItem={selectedFilter} />;
};
const getNotifications = createSelector([
(state: RootState) => state.notifications.items,
(_, topNotification?: string) => topNotification,
@ -53,7 +144,7 @@ const getNotifications = createSelector([
};
});
const Notifications = () => {
const NotificationsPage = () => {
const dispatch = useAppDispatch();
const features = useFeatures();
const intl = useIntl();
@ -208,4 +299,4 @@ const Notifications = () => {
);
};
export { Notifications as default };
export { NotificationsPage as default };

View File

@ -1,6 +1,6 @@
import clsx from 'clsx';
import React, { useRef, useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
@ -8,7 +8,10 @@ import Hashtag from 'pl-fe/components/hashtag';
import IconButton from 'pl-fe/components/icon-button';
import ScrollableList from 'pl-fe/components/scrollable-list';
import TrendingLink from 'pl-fe/components/trending-link';
import Column from 'pl-fe/components/ui/column';
import HStack from 'pl-fe/components/ui/hstack';
import Input from 'pl-fe/components/ui/input';
import SvgIcon from 'pl-fe/components/ui/svg-icon';
import Tabs from 'pl-fe/components/ui/tabs';
import Text from 'pl-fe/components/ui/text';
import AccountContainer from 'pl-fe/containers/account-container';
@ -28,12 +31,97 @@ import type { VirtuosoHandle } from 'react-virtuoso';
type SearchFilter = 'accounts' | 'hashtags' | 'statuses' | 'links';
const messages = defineMessages({
heading: { id: 'column.search', defaultMessage: 'Search' },
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
links: { id: 'search_results.links', defaultMessage: 'News' },
});
const SearchInput = () => {
const [params, setParams] = useSearchParams();
const [value, setValue] = useState(params.get('q') || '');
const intl = useIntl();
const setQuery = (value: string) => {
setParams(params => ({ ...Object.fromEntries(params.entries()), q: value }));
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
setValue(value);
};
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
if (params.get('q') === value) {
if (value.length > 0) {
setValue('');
setQuery('');
}
} else {
setQuery(value);
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
setQuery(value);
} else if (event.key === 'Escape') {
document.querySelector('.ui')?.parentElement?.focus();
}
};
return (
<div
className='sticky top-[76px] z-10 w-full bg-white/90 backdrop-blur black:bg-black/80 dark:bg-primary-900/90'
>
<label htmlFor='search' className='sr-only'>{intl.formatMessage(messages.placeholder)}</label>
<div className='relative'>
<Input
type='text'
id='search'
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
autoFocus
theme='search'
className='pr-10 rtl:pl-10 rtl:pr-3'
/>
<div
role='button'
tabIndex={0}
className='absolute inset-y-0 right-0 flex cursor-pointer items-center px-3 rtl:left-0 rtl:right-auto'
onClick={handleClick}
>
{params.get('q') === value ? (
<SvgIcon
src={require('@tabler/icons/outline/x.svg')}
className='size-4 text-gray-600'
aria-label={intl.formatMessage(messages.placeholder)}
/>
) : (
<SvgIcon
src={require('@tabler/icons/outline/search.svg')}
className='size-4 text-gray-600'
/>
)}
</div>
</div>
</div>
);
};
const SearchResults = () => {
const node = useRef<VirtuosoHandle>(null);
@ -274,4 +362,17 @@ const SearchResults = () => {
);
};
export { SearchResults as default };
const SearchPage = () => {
const intl = useIntl();
return (
<Column label={intl.formatMessage(messages.heading)}>
<div className='space-y-4'>
<SearchInput />
<SearchResults />
</div>
</Column>
);
};
export { SearchPage as default };

View File

@ -0,0 +1,126 @@
import React, { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { fetchFilters, deleteFilter } from 'pl-fe/actions/filters';
import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Button from 'pl-fe/components/ui/button';
import Column from 'pl-fe/components/ui/column';
import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import toast from 'pl-fe/toast';
const messages = defineMessages({
heading: { id: 'column.filters', defaultMessage: 'Muted words' },
home_timeline: { id: 'column.filters.home_timeline', defaultMessage: 'Home timeline' },
public_timeline: { id: 'column.filters.public_timeline', defaultMessage: 'Public timeline' },
notifications: { id: 'column.filters.notifications', defaultMessage: 'Notifications' },
conversations: { id: 'column.filters.conversations', defaultMessage: 'Conversations' },
accounts: { id: 'column.filters.accounts', defaultMessage: 'Accounts' },
delete_error: { id: 'column.filters.delete_error', defaultMessage: 'Error deleting filter' },
edit: { id: 'column.filters.edit', defaultMessage: 'Edit filter' },
delete: { id: 'column.filters.delete', defaultMessage: 'Delete' },
});
const contexts = {
home: messages.home_timeline,
public: messages.public_timeline,
notifications: messages.notifications,
thread: messages.conversations,
account: messages.accounts,
};
const FiltersPage = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const { filtersV2 } = useFeatures();
const filters = useAppSelector((state) => state.filters);
const handleFilterEdit = (id: string) => () => history.push(`/filters/${id}`);
const handleFilterDelete = (id: string) => () => {
dispatch(deleteFilter(id)).then(() => dispatch(fetchFilters())).catch(() => {
toast.error(intl.formatMessage(messages.delete_error));
});
};
useEffect(() => {
dispatch(fetchFilters());
}, []);
const emptyMessage = <FormattedMessage id='empty_column.filters' defaultMessage="You haven't created any muted words yet." />;
return (
<Column className='filter-settings-panel' label={intl.formatMessage(messages.heading)}>
<HStack className='mb-4' space={2} justifyContent='end'>
<Button
to='/filters/new'
theme='primary'
size='sm'
>
<FormattedMessage id='filters.create_filter' defaultMessage='Create filter' />
</Button>
</HStack>
<ScrollableList
scrollKey='filters'
emptyMessage={emptyMessage}
itemClassName='pb-4 last:pb-0'
>
{filters.map((filter) => (
<div key={filter.id} className='rounded-lg bg-gray-100 p-4 dark:bg-primary-800'>
<Stack space={2}>
<Stack className='grow' space={1}>
<Text weight='medium'>
<FormattedMessage id='filters.filters_list_phrases_label' defaultMessage='Keywords or phrases:' />
{' '}
<Text theme='muted' tag='span'>{filter.keywords.map(keyword => keyword.keyword).join(', ')}</Text>
</Text>
<Text weight='medium'>
<FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' />
{' '}
<Text theme='muted' tag='span'>{filter.context.map(context => contexts[context] ? intl.formatMessage(contexts[context]) : context).join(', ')}</Text>
</Text>
<HStack space={4} wrap>
<Text weight='medium'>
{filtersV2 ? (
filter.filter_action === 'hide' ?
<FormattedMessage id='filters.filters_list_hide_completely' defaultMessage='Hide content' /> :
<FormattedMessage id='filters.filters_list_warn' defaultMessage='Display warning' />
) : (filter.filter_action === 'hide' ?
<FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /> :
<FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' />)}
</Text>
{filter.expires_at && (
<Text weight='medium'>
{new Date(filter.expires_at).getTime() <= Date.now()
? <FormattedMessage id='filters.filters_list_expired' defaultMessage='Expired' />
: <RelativeTimestamp timestamp={filter.expires_at} className='whitespace-nowrap' futureDate />}
</Text>
)}
</HStack>
</Stack>
<HStack space={2} justifyContent='end'>
<Button theme='primary' onClick={handleFilterEdit(filter.id)}>
{intl.formatMessage(messages.edit)}
</Button>
<Button theme='danger' onClick={handleFilterDelete(filter.id)}>
{intl.formatMessage(messages.delete)}
</Button>
</HStack>
</Stack>
</div>
))}
</ScrollableList>
</Column>
);
};
export { FiltersPage as default };

View File

@ -1,126 +1,39 @@
import React, { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { fetchFilters, deleteFilter } from 'pl-fe/actions/filters';
import RelativeTimestamp from 'pl-fe/components/relative-timestamp';
import Hashtag from 'pl-fe/components/hashtag';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Button from 'pl-fe/components/ui/button';
import Column from 'pl-fe/components/ui/column';
import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useFeatures } from 'pl-fe/hooks/use-features';
import toast from 'pl-fe/toast';
import PlaceholderHashtag from 'pl-fe/features/placeholder/components/placeholder-hashtag';
import { useFollowedTags } from 'pl-fe/queries/hashtags/use-followed-tags';
const messages = defineMessages({
heading: { id: 'column.filters', defaultMessage: 'Muted words' },
home_timeline: { id: 'column.filters.home_timeline', defaultMessage: 'Home timeline' },
public_timeline: { id: 'column.filters.public_timeline', defaultMessage: 'Public timeline' },
notifications: { id: 'column.filters.notifications', defaultMessage: 'Notifications' },
conversations: { id: 'column.filters.conversations', defaultMessage: 'Conversations' },
accounts: { id: 'column.filters.accounts', defaultMessage: 'Accounts' },
delete_error: { id: 'column.filters.delete_error', defaultMessage: 'Error deleting filter' },
edit: { id: 'column.filters.edit', defaultMessage: 'Edit filter' },
delete: { id: 'column.filters.delete', defaultMessage: 'Delete' },
heading: { id: 'column.followed_tags', defaultMessage: 'Followed hashtags' },
});
const contexts = {
home: messages.home_timeline,
public: messages.public_timeline,
notifications: messages.notifications,
thread: messages.conversations,
account: messages.accounts,
};
const FiltersPage = () => {
const FollowedTagsPage = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const { filtersV2 } = useFeatures();
const filters = useAppSelector((state) => state.filters);
const { data: tags = [], isLoading, hasNextPage, fetchNextPage } = useFollowedTags();
const handleFilterEdit = (id: string) => () => history.push(`/filters/${id}`);
const handleFilterDelete = (id: string) => () => {
dispatch(deleteFilter(id)).then(() => dispatch(fetchFilters())).catch(() => {
toast.error(intl.formatMessage(messages.delete_error));
});
};
useEffect(() => {
dispatch(fetchFilters());
}, []);
const emptyMessage = <FormattedMessage id='empty_column.filters' defaultMessage="You haven't created any muted words yet." />;
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage="You haven't followed any hashtag yet." />;
return (
<Column className='filter-settings-panel' label={intl.formatMessage(messages.heading)}>
<HStack className='mb-4' space={2} justifyContent='end'>
<Button
to='/filters/new'
theme='primary'
size='sm'
>
<FormattedMessage id='filters.create_filter' defaultMessage='Create filter' />
</Button>
</HStack>
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='filters'
scrollKey='followedTags'
emptyMessage={emptyMessage}
itemClassName='pb-4 last:pb-0'
isLoading={isLoading}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
placeholderComponent={PlaceholderHashtag}
placeholderCount={5}
itemClassName='pb-3'
>
{filters.map((filter) => (
<div key={filter.id} className='rounded-lg bg-gray-100 p-4 dark:bg-primary-800'>
<Stack space={2}>
<Stack className='grow' space={1}>
<Text weight='medium'>
<FormattedMessage id='filters.filters_list_phrases_label' defaultMessage='Keywords or phrases:' />
{' '}
<Text theme='muted' tag='span'>{filter.keywords.map(keyword => keyword.keyword).join(', ')}</Text>
</Text>
<Text weight='medium'>
<FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' />
{' '}
<Text theme='muted' tag='span'>{filter.context.map(context => contexts[context] ? intl.formatMessage(contexts[context]) : context).join(', ')}</Text>
</Text>
<HStack space={4} wrap>
<Text weight='medium'>
{filtersV2 ? (
filter.filter_action === 'hide' ?
<FormattedMessage id='filters.filters_list_hide_completely' defaultMessage='Hide content' /> :
<FormattedMessage id='filters.filters_list_warn' defaultMessage='Display warning' />
) : (filter.filter_action === 'hide' ?
<FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /> :
<FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' />)}
</Text>
{filter.expires_at && (
<Text weight='medium'>
{new Date(filter.expires_at).getTime() <= Date.now()
? <FormattedMessage id='filters.filters_list_expired' defaultMessage='Expired' />
: <RelativeTimestamp timestamp={filter.expires_at} className='whitespace-nowrap' futureDate />}
</Text>
)}
</HStack>
</Stack>
<HStack space={2} justifyContent='end'>
<Button theme='primary' onClick={handleFilterEdit(filter.id)}>
{intl.formatMessage(messages.edit)}
</Button>
<Button theme='danger' onClick={handleFilterDelete(filter.id)}>
{intl.formatMessage(messages.delete)}
</Button>
</HStack>
</Stack>
</div>
))}
{tags.map(tag => <Hashtag key={tag.name} hashtag={tag} />)}
</ScrollableList>
</Column>
);
};
export { FiltersPage as default };
export { FollowedTagsPage as default };

View File

@ -73,7 +73,7 @@ const NewFolderForm: React.FC = () => {
);
};
const BookmarkFolders: React.FC = () => {
const BookmarkFoldersPage: React.FC = () => {
const intl = useIntl();
const features = useFeatures();
@ -128,4 +128,4 @@ const BookmarkFolders: React.FC = () => {
);
};
export { BookmarkFolders as default, NewFolderForm };
export { BookmarkFoldersPage as default, NewFolderForm };

View File

@ -7,8 +7,8 @@ import Stack from 'pl-fe/components/ui/stack';
import Tabs from 'pl-fe/components/ui/tabs';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { EditEvent } from './tabs/edit-event';
import { ManagePendingParticipants } from './tabs/manage-pending-participants';
import { EditEvent } from '../../features/compose-event/tabs/edit-event';
import { ManagePendingParticipants } from '../../features/compose-event/tabs/manage-pending-participants';
const messages = defineMessages({
manageEvent: { id: 'navigation_bar.manage_event', defaultMessage: 'Manage event' },
@ -21,11 +21,11 @@ type RouteParams = {
statusId?: string;
};
interface IComposeEvent {
interface IComposeEventPage {
params: RouteParams;
}
const ComposeEvent: React.FC<IComposeEvent> = ({ params }) => {
const ComposeEventPage: React.FC<IComposeEventPage> = ({ params }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@ -64,4 +64,4 @@ const ComposeEvent: React.FC<IComposeEvent> = ({ params }) => {
);
};
export { ComposeEvent as default };
export { ComposeEventPage as default };