@@ -1,10 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { defineMessages, FormattedDate, FormattedMessage, useIntl } from 'react-intl';
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { joinEvent, leaveEvent } from 'soapbox/actions/events';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { buildStatus } from 'soapbox/features/scheduled_statuses/builder';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import EventActionButton from 'soapbox/features/event/components/event-action-button';
|
||||
import EventDate from 'soapbox/features/event/components/event-date';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import Icon from './icon';
|
||||
import { Button, HStack, Stack, Text } from './ui';
|
||||
@@ -13,17 +12,17 @@ import VerificationBadge from './verification_badge';
|
||||
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' },
|
||||
leaveConfirm: { id: 'confirmations.leave_event.confirm', defaultMessage: 'Leave event' },
|
||||
leaveMessage: { id: 'confirmations.leave_event.message', defaultMessage: 'If you want to rejoin the event, the request will be manually reviewed again. Are you sure you want to proceed?' },
|
||||
});
|
||||
|
||||
interface IEventPreview {
|
||||
status: StatusEntity,
|
||||
status: StatusEntity
|
||||
}
|
||||
|
||||
const EventPreview: React.FC<IEventPreview> = ({ status }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const me = useAppSelector((state) => state.me);
|
||||
|
||||
@@ -32,112 +31,6 @@ const EventPreview: React.FC<IEventPreview> = ({ status }) => {
|
||||
|
||||
const banner = status.media_attachments?.find(({ description }) => description === 'Banner');
|
||||
|
||||
const handleJoin: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (event.join_mode === 'free') {
|
||||
dispatch(joinEvent(status.id));
|
||||
} else {
|
||||
dispatch(openModal('JOIN_EVENT', {
|
||||
statusId: status.id,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleLeave: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (event.join_mode === 'restricted') {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.leaveMessage),
|
||||
confirm: intl.formatMessage(messages.leaveConfirm),
|
||||
onConfirm: () => dispatch(leaveEvent(status.id)),
|
||||
}));
|
||||
} else {
|
||||
dispatch(leaveEvent(status.id));
|
||||
}
|
||||
};
|
||||
|
||||
const renderDate = useCallback(() => {
|
||||
if (!event.start_time) return null;
|
||||
|
||||
const startDate = new Date(event.start_time);
|
||||
|
||||
let date;
|
||||
|
||||
if (event.end_time) {
|
||||
const endDate = new Date(event.end_time);
|
||||
|
||||
const sameDay = startDate.getDate() === endDate.getDate() && startDate.getMonth() === endDate.getMonth() && startDate.getFullYear() === endDate.getFullYear();
|
||||
|
||||
if (sameDay) {
|
||||
date = (
|
||||
<>
|
||||
<FormattedDate value={event.start_time} year='2-digit' month='short' day='2-digit' weekday='short' hour='2-digit' minute='2-digit' />
|
||||
{' - '}
|
||||
<FormattedDate value={event.end_time} hour='2-digit' minute='2-digit' />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
date = (
|
||||
<>
|
||||
<FormattedDate value={event.start_time} year='2-digit' month='short' day='2-digit' weekday='short' />
|
||||
{' - '}
|
||||
<FormattedDate value={event.end_time} year='2-digit' month='short' day='2-digit' weekday='short' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
date = (
|
||||
<FormattedDate value={event.start_time} year='2-digit' month='short' day='2-digit' weekday='short' hour='2-digit' minute='2-digit' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack alignItems='center' space={1}>
|
||||
<Icon src={require('@tabler/icons/calendar.svg')} />
|
||||
<span>{date}</span>
|
||||
</HStack>
|
||||
);
|
||||
}, [event.start_time, event.end_time]);
|
||||
|
||||
const renderAction = useCallback(() => {
|
||||
let buttonLabel;
|
||||
let buttonIcon;
|
||||
let buttonDisabled = false;
|
||||
let buttonAction = handleLeave;
|
||||
|
||||
switch (event.join_state) {
|
||||
case 'accept':
|
||||
buttonLabel = <FormattedMessage id='event.join_state.accept' defaultMessage='Going' />;
|
||||
buttonIcon = require('@tabler/icons/check.svg');
|
||||
break;
|
||||
case 'pending':
|
||||
buttonLabel = <FormattedMessage id='event.join_state.pending' defaultMessage='Pending' />;
|
||||
break;
|
||||
case 'reject':
|
||||
buttonLabel = <FormattedMessage id='event.join_state.rejected' defaultMessage='Going' />;
|
||||
buttonIcon = require('@tabler/icons/ban.svg');
|
||||
buttonDisabled = true;
|
||||
break;
|
||||
default:
|
||||
buttonLabel = <FormattedMessage id='event.join_state.empty' defaultMessage='Participate' />;
|
||||
buttonAction = handleJoin;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
size='sm'
|
||||
theme='secondary'
|
||||
icon={buttonIcon}
|
||||
onClick={buttonAction}
|
||||
disabled={buttonDisabled}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
);
|
||||
}, [event.join_state]);
|
||||
|
||||
return (
|
||||
<div className='rounded-lg bg-gray-100 dark:bg-primary-800 shadow-xl relative overflow-hidden'>
|
||||
<div className='absolute top-28 right-3'>
|
||||
@@ -149,16 +42,16 @@ const EventPreview: React.FC<IEventPreview> = ({ status }) => {
|
||||
>
|
||||
<FormattedMessage id='event.manage' defaultMessage='Manage' />
|
||||
</Button>
|
||||
) : renderAction()}
|
||||
) : <EventActionButton status={status} />}
|
||||
</div>
|
||||
<div className='bg-primary-200 dark:bg-gray-600 h-40'>
|
||||
{banner && <img className='h-full w-full object-cover' src={banner.url} alt={banner.url} />}
|
||||
{banner && <img className='h-full w-full object-cover' src={banner.url} alt={intl.formatMessage(messages.bannerHeader)} />}
|
||||
</div>
|
||||
<Stack className='p-2.5' space={2}>
|
||||
<Text weight='semibold'>{event.name}</Text>
|
||||
|
||||
<div className='flex gap-y-1 gap-x-2 flex-wrap text-gray-700 dark:text-gray-600'>
|
||||
<HStack alignItems='center' space={1}>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Icon src={require('@tabler/icons/user.svg')} />
|
||||
<span>
|
||||
<span dangerouslySetInnerHTML={{ __html: account.display_name_html }} />
|
||||
@@ -166,10 +59,10 @@ const EventPreview: React.FC<IEventPreview> = ({ status }) => {
|
||||
</span>
|
||||
</HStack>
|
||||
|
||||
{renderDate()}
|
||||
<EventDate status={status} />
|
||||
|
||||
{event.location && (
|
||||
<HStack alignItems='center' space={1}>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Icon src={require('@tabler/icons/map-pin.svg')} />
|
||||
<span>
|
||||
{event.location.get('name')}
|
||||
|
||||
@@ -22,6 +22,8 @@ interface IStatusMedia {
|
||||
showMedia?: boolean,
|
||||
/** Callback when visibility is toggled (eg clicked through NSFW). */
|
||||
onToggleVisibility?: () => void,
|
||||
/** Whether or not to hide image describer as 'Banner' */
|
||||
excludeBanner?: boolean,
|
||||
}
|
||||
|
||||
/** Render media attachments for a status. */
|
||||
@@ -31,14 +33,17 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||
onClick,
|
||||
showMedia = true,
|
||||
onToggleVisibility = () => {},
|
||||
excludeBanner = false,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
|
||||
|
||||
const size = status.media_attachments.size;
|
||||
const firstAttachment = status.media_attachments.first();
|
||||
const mediaAttachments = excludeBanner ? status.media_attachments.filter(({ description }) => description !== 'Banner') : status.media_attachments;
|
||||
|
||||
let media = null;
|
||||
const size = mediaAttachments.size;
|
||||
const firstAttachment = mediaAttachments.first();
|
||||
|
||||
let media: JSX.Element | null = null;
|
||||
|
||||
const setRef = (c: HTMLDivElement): void => {
|
||||
if (c) {
|
||||
@@ -70,7 +75,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||
if (muted) {
|
||||
media = (
|
||||
<AttachmentThumbs
|
||||
media={status.media_attachments}
|
||||
media={mediaAttachments}
|
||||
onClick={onClick}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
@@ -99,7 +104,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={renderLoadingVideoPlayer} >
|
||||
<Bundle fetchComponent={Video} loading={renderLoadingVideoPlayer}>
|
||||
{(Component: any) => (
|
||||
<Component
|
||||
preview={video.preview_url}
|
||||
@@ -122,7 +127,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||
const attachment = firstAttachment;
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Audio} loading={renderLoadingAudioPlayer} >
|
||||
<Bundle fetchComponent={Audio} loading={renderLoadingAudioPlayer}>
|
||||
{(Component: any) => (
|
||||
<Component
|
||||
src={attachment.url}
|
||||
@@ -142,7 +147,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||
<Bundle fetchComponent={MediaGallery} loading={renderLoadingMediaGallery}>
|
||||
{(Component: any) => (
|
||||
<Component
|
||||
media={status.media_attachments}
|
||||
media={mediaAttachments}
|
||||
sensitive={status.sensitive}
|
||||
inReview={status.visibility === 'self'}
|
||||
height={285}
|
||||
|
||||
@@ -40,7 +40,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
|
||||
/** Right sidebar container in the UI. */
|
||||
const Aside: React.FC = ({ children }) => (
|
||||
<aside className='hidden xl:block xl:col-span-3'>
|
||||
<StickyBox offsetTop={80} className='space-y-6 pb-12' >
|
||||
<StickyBox offsetTop={80} className='space-y-6 pb-12'>
|
||||
{children}
|
||||
</StickyBox>
|
||||
</aside>
|
||||
|
||||
@@ -21,13 +21,15 @@ const justifyContentOptions = {
|
||||
|
||||
const alignItemsOptions = {
|
||||
center: 'items-center',
|
||||
start: 'items-start',
|
||||
end: 'items-end',
|
||||
};
|
||||
|
||||
interface IStack extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/** Size of the gap between elements. */
|
||||
space?: SIZES,
|
||||
/** Horizontal alignment of children. */
|
||||
alignItems?: 'center',
|
||||
alignItems?: 'center' | 'start' | 'end',
|
||||
/** Vertical alignment of children. */
|
||||
justifyContent?: 'center',
|
||||
/** Extra class names on the <div> element. */
|
||||
@@ -43,7 +45,7 @@ const Stack: React.FC<IStack> = (props) => {
|
||||
return (
|
||||
<div
|
||||
{...filteredProps}
|
||||
className={classNames('flex flex-col', {
|
||||
className={classNames('flex flex-col items', {
|
||||
// @ts-ignore
|
||||
[spaces[space]]: typeof space !== 'undefined',
|
||||
// @ts-ignore
|
||||
|
||||
Reference in New Issue
Block a user