Event pages?

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2022-09-08 23:25:02 +02:00
parent b97518d600
commit f7c09461fd
20 changed files with 1037 additions and 164 deletions

View File

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

View File

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

View File

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

View File

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