Create a logged-out call-to-action on threads

This commit is contained in:
Alex Gleason 2022-05-11 14:35:56 -05:00
parent 2ebf735884
commit 9f89c31bd3
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3 changed files with 59 additions and 13 deletions

View file

@ -47,7 +47,10 @@ interface ICardHeader {
onBackClick?: (event: React.MouseEvent) => void onBackClick?: (event: React.MouseEvent) => void
} }
/** Typically holds a CardTitle. */ /**
* Card header container with back button.
* Typically holds a CardTitle.
*/
const CardHeader: React.FC<ICardHeader> = ({ children, backHref, onBackClick }): JSX.Element => { const CardHeader: React.FC<ICardHeader> = ({ children, backHref, onBackClick }): JSX.Element => {
const intl = useIntl(); const intl = useIntl();

View file

@ -0,0 +1,36 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Card, CardTitle, Text, Stack, Button } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
/** Prompts logged-out users to log in when viewing a thread. */
const ThreadLoginCta: React.FC = () => {
const siteTitle = useAppSelector(state => state.instance.title);
return (
<Card className='px-6 py-12 space-y-6 text-center' variant='rounded'>
<Stack>
<CardTitle title={<FormattedMessage id='thread_login.title' defaultMessage='Continue the conversation' />} />
<Text>
<FormattedMessage
id='thread_login.message'
defaultMessage='Join {siteTitle} to get the full story and details.'
values={{ siteTitle }}
/>
</Text>
</Stack>
<Stack space={4} className='max-w-xs mx-auto'>
<Button theme='secondary' to='/login' block>
<FormattedMessage id='thread_login.login' defaultMessage='Log in' />
</Button>
<Button to='/signup' block>
<FormattedMessage id='thread_login.signup' defaultMessage='Sign up' />
</Button>
</Stack>
</Card>
);
};
export default ThreadLoginCta;

View file

@ -19,7 +19,7 @@ import { getSettings } from 'soapbox/actions/settings';
import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import ScrollableList from 'soapbox/components/scrollable_list'; import ScrollableList from 'soapbox/components/scrollable_list';
import SubNavigation from 'soapbox/components/sub_navigation'; import SubNavigation from 'soapbox/components/sub_navigation';
import { Column } from 'soapbox/components/ui'; import { Column, Stack } from 'soapbox/components/ui';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
import PendingStatus from 'soapbox/features/ui/components/pending_status'; import PendingStatus from 'soapbox/features/ui/components/pending_status';
@ -60,6 +60,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
import ActionBar from './components/action-bar'; import ActionBar from './components/action-bar';
import DetailedStatus from './components/detailed-status'; import DetailedStatus from './components/detailed-status';
import ThreadLoginCta from './components/thread-login-cta';
import ThreadStatus from './components/thread-status'; import ThreadStatus from './components/thread-status';
import type { AxiosError } from 'axios'; import type { AxiosError } from 'axios';
@ -72,6 +73,7 @@ import type {
Attachment as AttachmentEntity, Attachment as AttachmentEntity,
Status as StatusEntity, Status as StatusEntity,
} from 'soapbox/types/entities'; } from 'soapbox/types/entities';
import type { Me } from 'soapbox/types/soapbox';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'status.title', defaultMessage: '@{username}\'s Post' }, title: { id: 'status.title', defaultMessage: '@{username}\'s Post' },
@ -181,6 +183,7 @@ interface IStatus extends RouteComponentProps, IntlComponentProps {
allowedEmoji: ImmutableList<string>, allowedEmoji: ImmutableList<string>,
onOpenMedia: (media: ImmutableList<AttachmentEntity>, index: number) => void, onOpenMedia: (media: ImmutableList<AttachmentEntity>, index: number) => void,
onOpenVideo: (video: AttachmentEntity, time: number) => void, onOpenVideo: (video: AttachmentEntity, time: number) => void,
me: Me,
} }
interface IStatusState { interface IStatusState {
@ -669,7 +672,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
} }
render() { render() {
const { status, ancestorsIds, descendantsIds, intl } = this.props; const { me, status, ancestorsIds, descendantsIds, intl } = this.props;
const hasAncestors = ancestorsIds && ancestorsIds.size > 0; const hasAncestors = ancestorsIds && ancestorsIds.size > 0;
const hasDescendants = descendantsIds && descendantsIds.size > 0; const hasDescendants = descendantsIds && descendantsIds.size > 0;
@ -782,16 +785,20 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
<SubNavigation message={intl.formatMessage(titleMessage, { username })} /> <SubNavigation message={intl.formatMessage(titleMessage, { username })} />
</div> </div>
<div ref={this.setRef} className='thread'> <Stack space={2}>
<ScrollableList <div ref={this.setRef} className='thread'>
onRefresh={this.handleRefresh} <ScrollableList
hasMore={!!this.state.next} onRefresh={this.handleRefresh}
onLoadMore={this.handleLoadMore} hasMore={!!this.state.next}
placeholderComponent={() => <PlaceholderStatus thread />} onLoadMore={this.handleLoadMore}
> placeholderComponent={() => <PlaceholderStatus thread />}
{children} >
</ScrollableList> {children}
</div> </ScrollableList>
</div>
{!me && <ThreadLoginCta />}
</Stack>
</Column> </Column>
); );
} }