diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx
index 1d670a344..cc91ded59 100644
--- a/app/soapbox/components/account.tsx
+++ b/app/soapbox/components/account.tsx
@@ -56,6 +56,7 @@ interface IAccount {
showProfileHoverCard?: boolean,
timestamp?: string | Date,
timestampUrl?: string,
+ futureTimestamp?: boolean,
withDate?: boolean,
withRelationship?: boolean,
showEdit?: boolean,
@@ -75,6 +76,7 @@ const Account = ({
showProfileHoverCard = true,
timestamp,
timestampUrl,
+ futureTimestamp = false,
withDate = false,
withRelationship = true,
showEdit = false,
@@ -205,10 +207,10 @@ const Account = ({
{timestampUrl ? (
-
+
) : (
-
+
)}
>
) : null}
diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx
index 44dac93c7..803bbd7c1 100644
--- a/app/soapbox/components/ui/hstack/hstack.tsx
+++ b/app/soapbox/components/ui/hstack/hstack.tsx
@@ -4,6 +4,8 @@ import React from 'react';
const justifyContentOptions = {
between: 'justify-between',
center: 'justify-center',
+ start: 'justify-start',
+ end: 'justify-end',
};
const alignItemsOptions = {
@@ -29,7 +31,7 @@ interface IHStack {
/** Extra class names on the
element. */
className?: string,
/** Horizontal alignment of children. */
- justifyContent?: 'between' | 'center',
+ justifyContent?: 'between' | 'center' | 'start' | 'end',
/** Size of the gap between elements. */
space?: 0.5 | 1 | 1.5 | 2 | 3 | 4 | 6,
/** Whether to let the flexbox grow. */
diff --git a/app/soapbox/features/scheduled_statuses/builder.js b/app/soapbox/features/scheduled_statuses/builder.tsx
similarity index 85%
rename from app/soapbox/features/scheduled_statuses/builder.js
rename to app/soapbox/features/scheduled_statuses/builder.tsx
index 8e3417582..2927c8984 100644
--- a/app/soapbox/features/scheduled_statuses/builder.js
+++ b/app/soapbox/features/scheduled_statuses/builder.tsx
@@ -3,11 +3,13 @@ import { Map as ImmutableMap } from 'immutable';
import { normalizeStatus } from 'soapbox/normalizers/status';
import { calculateStatus } from 'soapbox/reducers/statuses';
import { makeGetAccount } from 'soapbox/selectors';
+import { RootState } from 'soapbox/store';
-export const buildStatus = (state, scheduledStatus) => {
+export const buildStatus = (state: RootState, scheduledStatus: ImmutableMap
) => {
const getAccount = makeGetAccount();
- const me = state.get('me');
+ const me = state.me as string;
+
const params = scheduledStatus.get('params');
const account = getAccount(state, me);
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status.js b/app/soapbox/features/scheduled_statuses/components/scheduled_status.js
deleted file mode 100644
index b88ccc450..000000000
--- a/app/soapbox/features/scheduled_statuses/components/scheduled_status.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import classNames from 'classnames';
-import React from 'react';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
-import { Link, NavLink } from 'react-router-dom';
-
-import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
-import Avatar from 'soapbox/components/avatar';
-import DisplayName from 'soapbox/components/display-name';
-import RelativeTimestamp from 'soapbox/components/relative_timestamp';
-import StatusContent from 'soapbox/components/status_content';
-import StatusReplyMentions from 'soapbox/components/status_reply_mentions';
-import PollPreview from 'soapbox/features/ui/components/poll_preview';
-import { getDomain } from 'soapbox/utils/accounts';
-
-import { buildStatus } from '../builder';
-
-import ScheduledStatusActionBar from './scheduled_status_action_bar';
-
-const mapStateToProps = (state, props) => {
- const scheduledStatus = state.getIn(['scheduled_statuses', props.statusId]);
- return {
- status: buildStatus(state, scheduledStatus),
- };
-};
-
-export default @connect(mapStateToProps)
-class ScheduledStatus extends ImmutablePureComponent {
-
- render() {
- const { status, account, ...other } = this.props;
- if (!status.get('account')) return null;
-
- const statusUrl = `/scheduled_statuses/${status.get('id')}`;
- const favicon = status.getIn(['account', 'pleroma', 'favicon']);
- const domain = getDomain(status.get('account'));
-
- return (
-
-
-
-
-
-
-
-
-
- {favicon &&
-
-
-
-
-
}
-
-
-
-
-
-
-
-
- {status.get('media_attachments').size > 0 && (
-
- )}
-
- {status.get('poll') &&
}
-
-
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status.tsx b/app/soapbox/features/scheduled_statuses/components/scheduled_status.tsx
new file mode 100644
index 000000000..0d8b14cd3
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/components/scheduled_status.tsx
@@ -0,0 +1,66 @@
+import classNames from 'classnames';
+import React from 'react';
+
+import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
+import StatusContent from 'soapbox/components/status_content';
+import StatusReplyMentions from 'soapbox/components/status_reply_mentions';
+import { HStack } from 'soapbox/components/ui';
+import AccountContainer from 'soapbox/containers/account_container';
+import PollPreview from 'soapbox/features/ui/components/poll_preview';
+import { useAppSelector } from 'soapbox/hooks';
+
+import { buildStatus } from '../builder';
+
+import ScheduledStatusActionBar from './scheduled_status_action_bar';
+
+import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
+
+interface IScheduledStatus {
+ statusId: string,
+}
+
+const ScheduledStatus: React.FC = ({ statusId, ...other }) => {
+ const status = useAppSelector((state) => buildStatus(state, state.scheduled_statuses.get(statusId))) as StatusEntity;
+
+ if (!status) return null;
+
+ const account = status.account as AccountEntity;
+
+ return (
+
+
+
+
+
+
+
+
+ {status.media_attachments.size > 0 && (
+
+ )}
+
+ {status.poll &&
}
+
+
+
+
+ );
+};
+
+export default ScheduledStatus;
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js b/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js
deleted file mode 100644
index 7ed5c4d08..000000000
--- a/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { openModal } from 'soapbox/actions/modals';
-import { cancelScheduledStatus } from 'soapbox/actions/scheduled_statuses';
-import { getSettings } from 'soapbox/actions/settings';
-import IconButton from 'soapbox/components/icon_button';
-import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
-
-const messages = defineMessages({
- cancel: { id: 'scheduled_status.cancel', defaultMessage: 'Cancel' },
- deleteConfirm: { id: 'confirmations.scheduled_status_delete.confirm', defaultMessage: 'Cancel' },
- deleteHeading: { id: 'confirmations.scheduled_status_delete.heading', defaultMessage: 'Cancel scheduled post' },
- deleteMessage: { id: 'confirmations.scheduled_status_delete.message', defaultMessage: 'Are you sure you want to cancel this scheduled post?' },
-});
-
-const mapStateToProps = state => {
- const me = state.get('me');
- return {
- me,
- };
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
- onCancelClick: (status) => {
- dispatch((_, getState) => {
-
- const deleteModal = getSettings(getState()).get('deleteModal');
- if (!deleteModal) {
- dispatch(cancelScheduledStatus(status.get('id')));
- } else {
- dispatch(openModal('CONFIRM', {
- icon: require('@tabler/icons/icons/calendar-stats.svg'),
- heading: intl.formatMessage(messages.deleteHeading),
- message: intl.formatMessage(messages.deleteMessage),
- confirm: intl.formatMessage(messages.deleteConfirm),
- onConfirm: () => dispatch(cancelScheduledStatus(status.get('id'))),
- }));
- }
- });
- },
-});
-
-class ScheduledStatusActionBar extends ImmutablePureComponent {
-
- static propTypes = {
- status: ImmutablePropTypes.record.isRequired,
- intl: PropTypes.object.isRequired,
- me: SoapboxPropTypes.me,
- onCancelClick: PropTypes.func.isRequired,
- };
-
- handleCancelClick = e => {
- const { status, onCancelClick } = this.props;
-
- onCancelClick(status);
- }
-
- render() {
- const { intl } = this.props;
-
- return (
-
- );
- }
-
-}
-
-
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScheduledStatusActionBar));
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.tsx b/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.tsx
new file mode 100644
index 000000000..7b44805d4
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+
+import { openModal } from 'soapbox/actions/modals';
+import { cancelScheduledStatus } from 'soapbox/actions/scheduled_statuses';
+import { getSettings } from 'soapbox/actions/settings';
+import IconButton from 'soapbox/components/icon_button';
+import { HStack } from 'soapbox/components/ui';
+import { useAppDispatch } from 'soapbox/hooks';
+
+import type { Status as StatusEntity } from 'soapbox/types/entities';
+
+const messages = defineMessages({
+ cancel: { id: 'scheduled_status.cancel', defaultMessage: 'Cancel' },
+ deleteConfirm: { id: 'confirmations.scheduled_status_delete.confirm', defaultMessage: 'Cancel' },
+ deleteHeading: { id: 'confirmations.scheduled_status_delete.heading', defaultMessage: 'Cancel scheduled post' },
+ deleteMessage: { id: 'confirmations.scheduled_status_delete.message', defaultMessage: 'Are you sure you want to cancel this scheduled post?' },
+});
+
+interface IScheduledStatusActionBar {
+ status: StatusEntity,
+}
+
+const ScheduledStatusActionBar: React.FC = ({ status }) => {
+ const intl = useIntl();
+
+ const dispatch = useAppDispatch();
+
+ const handleCancelClick = () => {
+ dispatch((_, getState) => {
+
+ const deleteModal = getSettings(getState()).get('deleteModal');
+ if (!deleteModal) {
+ dispatch(cancelScheduledStatus(status.id));
+ } else {
+ dispatch(openModal('CONFIRM', {
+ icon: require('@tabler/icons/icons/calendar-stats.svg'),
+ heading: intl.formatMessage(messages.deleteHeading),
+ message: intl.formatMessage(messages.deleteMessage),
+ confirm: intl.formatMessage(messages.deleteConfirm),
+ onConfirm: () => dispatch(cancelScheduledStatus(status.id)),
+ }));
+ }
+ });
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default ScheduledStatusActionBar;
diff --git a/app/soapbox/features/scheduled_statuses/index.js b/app/soapbox/features/scheduled_statuses/index.js
deleted file mode 100644
index b8a63497e..000000000
--- a/app/soapbox/features/scheduled_statuses/index.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { debounce } from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import ScrollableList from 'soapbox/components/scrollable_list';
-
-import { fetchScheduledStatuses, expandScheduledStatuses } from '../../actions/scheduled_statuses';
-import Column from '../ui/components/column';
-
-import ScheduledStatus from './components/scheduled_status';
-
-const messages = defineMessages({
- heading: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled Posts' },
-});
-
-const mapStateToProps = state => ({
- statusIds: state.getIn(['status_lists', 'scheduled_statuses', 'items']),
- isLoading: state.getIn(['status_lists', 'scheduled_statuses', 'isLoading'], true),
- hasMore: !!state.getIn(['status_lists', 'scheduled_statuses', 'next']),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class ScheduledStatuses extends ImmutablePureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- statusIds: ImmutablePropTypes.orderedSet.isRequired,
- intl: PropTypes.object.isRequired,
- hasMore: PropTypes.bool,
- isLoading: PropTypes.bool,
- };
-
- componentDidMount() {
- const { dispatch } = this.props;
- dispatch(fetchScheduledStatuses());
- }
-
- handleLoadMore = debounce(() => {
- this.props.dispatch(expandScheduledStatuses());
- }, 300, { leading: true })
-
-
- render() {
- const { intl, statusIds, hasMore, isLoading } = this.props;
- const emptyMessage = ;
-
- return (
-
-
- {statusIds.map(id => )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/scheduled_statuses/index.tsx b/app/soapbox/features/scheduled_statuses/index.tsx
new file mode 100644
index 000000000..7d387022d
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/index.tsx
@@ -0,0 +1,51 @@
+import { debounce } from 'lodash';
+import React from 'react';
+import { useEffect } from 'react';
+import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
+
+import { fetchScheduledStatuses, expandScheduledStatuses } from 'soapbox/actions/scheduled_statuses';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
+
+import Column from '../ui/components/column';
+
+import ScheduledStatus from './components/scheduled_status';
+
+const messages = defineMessages({
+ heading: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled Posts' },
+});
+
+const handleLoadMore = debounce((dispatch) => {
+ dispatch(expandScheduledStatuses());
+}, 300, { leading: true });
+
+const ScheduledStatuses = () => {
+ const intl = useIntl();
+ const dispatch = useAppDispatch();
+
+ const statusIds = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'items']));
+ const isLoading = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'isLoading']));
+ const hasMore = useAppSelector((state) => !!state.status_lists.getIn(['scheduled_statuses', 'next']));
+
+ useEffect(() => {
+ dispatch(fetchScheduledStatuses());
+ }, []);
+
+ const emptyMessage = ;
+
+ return (
+
+ handleLoadMore(dispatch)}
+ emptyMessage={emptyMessage}
+ >
+ {statusIds.map((id: string) => )}
+
+
+ );
+};
+
+export default ScheduledStatuses;
\ No newline at end of file