diff --git a/app/soapbox/actions/events.ts b/app/soapbox/actions/events.ts index cf911e385..28d5b9db7 100644 --- a/app/soapbox/actions/events.ts +++ b/app/soapbox/actions/events.ts @@ -4,7 +4,7 @@ import api, { getLinks } from 'soapbox/api'; import { formatBytes } from 'soapbox/utils/media'; import resizeImage from 'soapbox/utils/resize-image'; -import { importFetchedAccounts, importFetchedStatus } from './importer'; +import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; import { fetchMedia, uploadMedia } from './media'; import { closeModal, openModal } from './modals'; import snackbar from './snackbar'; @@ -76,6 +76,13 @@ const EVENT_COMPOSE_CANCEL = 'EVENT_COMPOSE_CANCEL'; const EVENT_FORM_SET = 'EVENT_FORM_SET'; +const RECENT_EVENTS_FETCH_REQUEST = 'RECENT_EVENTS_FETCH_REQUEST'; +const RECENT_EVENTS_FETCH_SUCCESS = 'RECENT_EVENTS_FETCH_SUCCESS'; +const RECENT_EVENTS_FETCH_FAIL = 'RECENT_EVENTS_FETCH_FAIL'; +const JOINED_EVENTS_FETCH_REQUEST = 'JOINED_EVENTS_FETCH_REQUEST'; +const JOINED_EVENTS_FETCH_SUCCESS = 'JOINED_EVENTS_FETCH_SUCCESS'; +const JOINED_EVENTS_FETCH_FAIL = 'JOINED_EVENTS_FETCH_FAIL'; + const noOp = () => new Promise(f => f(undefined)); const messages = defineMessages({ @@ -579,6 +586,48 @@ const editEvent = (id: string) => (dispatch: AppDispatch, getState: () => RootSt }); }; +const fetchRecentEvents = () => + (dispatch: AppDispatch, getState: () => RootState) => { + if (getState().status_lists.get('recent_events')?.isLoading) { + return; + } + + dispatch({ type: RECENT_EVENTS_FETCH_REQUEST }); + + api(getState).get('/api/v1/timelines/public?only_events=true').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); + dispatch({ + type: RECENT_EVENTS_FETCH_SUCCESS, + statuses: response.data, + next: next ? next.uri : null, + }); + }).catch(error => { + dispatch({ type: RECENT_EVENTS_FETCH_FAIL, error }); + }); + }; + +const fetchJoinedEvents = () => + (dispatch: AppDispatch, getState: () => RootState) => { + if (getState().status_lists.get('joined_events')?.isLoading) { + return; + } + + dispatch({ type: JOINED_EVENTS_FETCH_REQUEST }); + + api(getState).get('/api/v1/pleroma/events/joined_events').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); + dispatch({ + type: JOINED_EVENTS_FETCH_SUCCESS, + statuses: response.data, + next: next ? next.uri : null, + }); + }).catch(error => { + dispatch({ type: JOINED_EVENTS_FETCH_FAIL, error }); + }); + }; + export { LOCATION_SEARCH_REQUEST, LOCATION_SEARCH_SUCCESS, @@ -624,6 +673,12 @@ export { EVENT_PARTICIPATION_REQUEST_REJECT_FAIL, EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, + RECENT_EVENTS_FETCH_REQUEST, + RECENT_EVENTS_FETCH_SUCCESS, + RECENT_EVENTS_FETCH_FAIL, + JOINED_EVENTS_FETCH_REQUEST, + JOINED_EVENTS_FETCH_SUCCESS, + JOINED_EVENTS_FETCH_FAIL, locationSearch, changeEditEventName, changeEditEventDescription, @@ -677,4 +732,6 @@ export { fetchEventIcs, cancelEventCompose, editEvent, + fetchRecentEvents, + fetchJoinedEvents, }; diff --git a/app/soapbox/components/event-preview.tsx b/app/soapbox/components/event-preview.tsx index 3b4e27031..74dd69021 100644 --- a/app/soapbox/components/event-preview.tsx +++ b/app/soapbox/components/event-preview.tsx @@ -35,7 +35,7 @@ const EventPreview: React.FC = ({ status, className, hideAction } const banner = status.media_attachments?.find(({ description }) => description === 'Banner'); return ( -
+
{!hideAction && (account.id === me ? ( +
+ )} + + {statusIds.map(statusId => )} + + {index !== statusIds.size - 1 && ( +
+ +
+ )} +
+ ); +}; + +export default EventCarousel; diff --git a/app/soapbox/features/events/index.tsx b/app/soapbox/features/events/index.tsx index b1aebe0e2..8115f10e0 100644 --- a/app/soapbox/features/events/index.tsx +++ b/app/soapbox/features/events/index.tsx @@ -1,7 +1,12 @@ -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import React, { useEffect, useState } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { Column } from 'soapbox/components/ui'; +import { fetchJoinedEvents, fetchRecentEvents } from 'soapbox/actions/events'; +import { openModal } from 'soapbox/actions/modals'; +import { Button, CardBody, CardHeader, CardTitle, Column, HStack } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; + +import EventCarousel from './components/event-carousel'; const messages = defineMessages({ title: { id: 'column.events', defaultMessage: 'Events' }, @@ -10,8 +15,53 @@ const messages = defineMessages({ const Events = () => { const intl = useIntl(); + const dispatch = useAppDispatch(); + + const recentEvents = useAppSelector((state) => state.status_lists.get('recent_events')!.items); + const recentEventsLoading = useAppSelector((state) => state.status_lists.get('recent_events')!.isLoading); + const joinedEvents = useAppSelector((state) => state.status_lists.get('joined_events')!.items); + const joinedEventsLoading = useAppSelector((state) => state.status_lists.get('joined_events')!.isLoading); + + const onComposeEvent = () => { + dispatch(openModal('COMPOSE_EVENT')); + }; + + useEffect(() => { + dispatch(fetchRecentEvents()); + dispatch(fetchJoinedEvents()); + }, []); + return ( - + + + + + + + } + /> + + + + + + } + /> + + ); }; diff --git a/app/soapbox/features/placeholder/components/placeholder-event-preview.tsx b/app/soapbox/features/placeholder/components/placeholder-event-preview.tsx new file mode 100644 index 000000000..ad706c55e --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder-event-preview.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { Stack, Text } from 'soapbox/components/ui'; + +import { generateText, randomIntFromInterval } from '../utils'; + +const PlaceholderEventPreview = () => { + const eventNameLength = randomIntFromInterval(5, 25); + const nameLength = randomIntFromInterval(5, 15); + + return ( +
+
+ {/* {intl.formatMessage(messages.bannerHeader)}} */} +
+ + {generateText(eventNameLength)} + +
+ {generateText(nameLength)} + {generateText(nameLength)} + {generateText(nameLength)} +
+
+
+ ); +}; + +export default PlaceholderEventPreview; diff --git a/app/soapbox/features/ui/components/link-footer.tsx b/app/soapbox/features/ui/components/link-footer.tsx index bc1c67fd3..5383be4c9 100644 --- a/app/soapbox/features/ui/components/link-footer.tsx +++ b/app/soapbox/features/ui/components/link-footer.tsx @@ -43,6 +43,9 @@ const LinkFooter: React.FC = (): JSX.Element => { {features.profileDirectory && ( )} + {features.events && ( + + )} {features.filters && ( diff --git a/app/soapbox/reducers/status-lists.ts b/app/soapbox/reducers/status-lists.ts index 3a19c5eea..1f743ea16 100644 --- a/app/soapbox/reducers/status-lists.ts +++ b/app/soapbox/reducers/status-lists.ts @@ -12,6 +12,14 @@ import { BOOKMARKED_STATUSES_EXPAND_SUCCESS, BOOKMARKED_STATUSES_EXPAND_FAIL, } from '../actions/bookmarks'; +import { + RECENT_EVENTS_FETCH_REQUEST, + RECENT_EVENTS_FETCH_SUCCESS, + RECENT_EVENTS_FETCH_FAIL, + JOINED_EVENTS_FETCH_REQUEST, + JOINED_EVENTS_FETCH_SUCCESS, + JOINED_EVENTS_FETCH_FAIL, +} from '../actions/events'; import { FAVOURITED_STATUSES_FETCH_REQUEST, FAVOURITED_STATUSES_FETCH_SUCCESS, @@ -68,6 +76,8 @@ const initialState: State = ImmutableMap({ bookmarks: StatusListRecord(), pins: StatusListRecord(), scheduled_statuses: StatusListRecord(), + recent_events: StatusListRecord(), + joined_events: StatusListRecord(), }); const getStatusId = (status: string | StatusEntity) => typeof status === 'string' ? status : status.id; @@ -168,6 +178,18 @@ export default function statusLists(state = initialState, action: AnyAction) { case SCHEDULED_STATUS_CANCEL_REQUEST: case SCHEDULED_STATUS_CANCEL_SUCCESS: return removeOneFromList(state, 'scheduled_statuses', action.id || action.status.id); + case RECENT_EVENTS_FETCH_REQUEST: + return setLoading(state, 'recent_events', true); + case RECENT_EVENTS_FETCH_FAIL: + return setLoading(state, 'recent_events', false); + case RECENT_EVENTS_FETCH_SUCCESS: + return normalizeList(state, 'recent_events', action.statuses, action.next); + case JOINED_EVENTS_FETCH_REQUEST: + return setLoading(state, 'joined_events', true); + case JOINED_EVENTS_FETCH_FAIL: + return setLoading(state, 'joined_events', false); + case JOINED_EVENTS_FETCH_SUCCESS: + return normalizeList(state, 'joined_events', action.statuses, action.next); default: return state; }