wip with channel prompts on comments
This commit is contained in:
parent
5beb219ff6
commit
a5107f075c
9 changed files with 101 additions and 37 deletions
|
@ -2,6 +2,7 @@
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import TagsSearch from 'component/tagsSearch';
|
import TagsSearch from 'component/tagsSearch';
|
||||||
|
@ -70,6 +71,7 @@ function ChannelForm(props: Props) {
|
||||||
createError,
|
createError,
|
||||||
clearChannelErrors,
|
clearChannelErrors,
|
||||||
openModal,
|
openModal,
|
||||||
|
disabled,
|
||||||
} = props;
|
} = props;
|
||||||
const [nameError, setNameError] = React.useState(undefined);
|
const [nameError, setNameError] = React.useState(undefined);
|
||||||
const [bidError, setBidError] = React.useState('');
|
const [bidError, setBidError] = React.useState('');
|
||||||
|
@ -180,7 +182,7 @@ function ChannelForm(props: Props) {
|
||||||
// TODO clear and bail after submit
|
// TODO clear and bail after submit
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="main--contained">
|
<div className={classnames('main--contained', { 'card--disabled': disabled })}>
|
||||||
<header className="channel-cover">
|
<header className="channel-cover">
|
||||||
<div className="channel__quick-actions">
|
<div className="channel__quick-actions">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { SIMPLE_SITE, SITE_NAME } from 'config';
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import { CHANNEL_NEW } from 'constants/claim';
|
import { CHANNEL_NEW } from 'constants/claim';
|
||||||
import { SIMPLE_SITE } from 'config';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelSelection from 'component/selectChannel';
|
import ChannelSelection from 'component/selectChannel';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import * as MODALS from 'constants/modal_types';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ export function CommentCreate(props: Props) {
|
||||||
const [channel, setChannel] = usePersistedState('comment-channel', '');
|
const [channel, setChannel] = usePersistedState('comment-channel', '');
|
||||||
const [charCount, setCharCount] = useState(commentValue.length);
|
const [charCount, setCharCount] = useState(commentValue.length);
|
||||||
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
|
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
|
||||||
|
const hasChannels = channels && channels.length;
|
||||||
|
|
||||||
const topChannel =
|
const topChannel =
|
||||||
channels &&
|
channels &&
|
||||||
|
@ -104,6 +106,18 @@ export function CommentCreate(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasChannels) {
|
||||||
|
return (
|
||||||
|
<div className="notice-message">
|
||||||
|
<h3 className="section__title">{__('Join the discussion')}</h3>
|
||||||
|
<p className="section__subtitle">{__('A channel is required to comment on %SITE_NAME%.', { SITE_NAME })}</p>
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button button="primary" label={__('Create a channel')} navigate={`/$/${PAGES.CHANNEL_NEW}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimIsMine, selectMyChannelClaims } from 'lbry-redux';
|
import { makeSelectClaimIsMine, selectMyChannelClaims } from 'lbry-redux';
|
||||||
import { makeSelectRepliesForParentId } from 'redux/selectors/comments';
|
import { makeSelectRepliesForParentId } from 'redux/selectors/comments';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import CommentsReplies from './view';
|
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
import CommentsReplies from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
myChannels: selectMyChannelClaims(state),
|
myChannels: selectMyChannelClaims(state),
|
||||||
|
@ -12,4 +12,6 @@ const select = (state, props) => ({
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, null)(CommentsReplies);
|
export default connect(select, {
|
||||||
|
doToast,
|
||||||
|
})(CommentsReplies);
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { SITE_NAME } from 'config';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Comment from 'component/comment';
|
import Comment from 'component/comment';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import CommentCreate from 'component/commentCreate';
|
import CommentCreate from 'component/commentCreate';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comments: Array<any>,
|
comments: Array<any>,
|
||||||
|
@ -13,31 +16,35 @@ type Props = {
|
||||||
linkedComment?: Comment,
|
linkedComment?: Comment,
|
||||||
parentId: string,
|
parentId: string,
|
||||||
commentingEnabled: boolean,
|
commentingEnabled: boolean,
|
||||||
|
doToast: ({ message: string }) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CommentsReplies(props: Props) {
|
function CommentsReplies(props: Props) {
|
||||||
const { uri, comments, claimIsMine, myChannels, linkedComment, parentId, commentingEnabled } = props;
|
const { uri, comments, claimIsMine, myChannels, linkedComment, parentId, commentingEnabled, doToast } = props;
|
||||||
|
const {
|
||||||
|
push,
|
||||||
|
location: { pathname },
|
||||||
|
} = useHistory();
|
||||||
const [isReplying, setReplying] = React.useState(false);
|
const [isReplying, setReplying] = React.useState(false);
|
||||||
const [isExpanded, setExpanded] = React.useState(false);
|
const [isExpanded, setExpanded] = React.useState(false);
|
||||||
const [start, setStart] = React.useState(0);
|
const [start, setStart] = React.useState(0);
|
||||||
const [end, setEnd] = React.useState(9);
|
const [end, setEnd] = React.useState(9);
|
||||||
const sortedComments = comments ? [...comments].reverse() : [];
|
const sortedComments = comments ? [...comments].reverse() : [];
|
||||||
const numberOfComments = comments ? comments.length : 0;
|
const numberOfComments = comments ? comments.length : 0;
|
||||||
|
const linkedCommentId = linkedComment ? linkedComment.comment_id : '';
|
||||||
|
const commentsIndexOfLInked = comments && sortedComments.findIndex(e => e.comment_id === linkedCommentId);
|
||||||
|
const hasChannels = myChannels && myChannels.length > 0;
|
||||||
|
|
||||||
const showMore = () => {
|
function showMore() {
|
||||||
if (start > 0) {
|
if (start > 0) {
|
||||||
setStart(0);
|
setStart(0);
|
||||||
} else {
|
} else {
|
||||||
setEnd(numberOfComments);
|
setEnd(numberOfComments);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const linkedCommentId = linkedComment ? linkedComment.comment_id : '';
|
|
||||||
|
|
||||||
const commentsIndexOfLInked = comments && sortedComments.findIndex(e => e.comment_id === linkedCommentId);
|
|
||||||
|
|
||||||
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
|
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
|
||||||
const isMyComment = (channelId: string) => {
|
function isMyComment(channelId: string) {
|
||||||
if (myChannels != null && channelId != null) {
|
if (myChannels != null && channelId != null) {
|
||||||
for (let i = 0; i < myChannels.length; i++) {
|
for (let i = 0; i < myChannels.length; i++) {
|
||||||
if (myChannels[i].claim_id === channelId) {
|
if (myChannels[i].claim_id === channelId) {
|
||||||
|
@ -46,16 +53,25 @@ function CommentsReplies(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCommentDone = () => {
|
function handleCommentDone() {
|
||||||
if (!isExpanded) {
|
if (!isExpanded) {
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
setStart(numberOfComments || 0);
|
setStart(numberOfComments || 0);
|
||||||
}
|
}
|
||||||
setEnd(numberOfComments + 1);
|
setEnd(numberOfComments + 1);
|
||||||
setReplying(false);
|
setReplying(false);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function handleCommentReply() {
|
||||||
|
if (!hasChannels) {
|
||||||
|
push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`);
|
||||||
|
doToast({ message: __('A channel is required to comment on %SITE_NAME%', { SITE_NAME }) });
|
||||||
|
} else {
|
||||||
|
setReplying(!isReplying);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
@ -75,13 +91,13 @@ function CommentsReplies(props: Props) {
|
||||||
const displayedComments = sortedComments.slice(start, end);
|
const displayedComments = sortedComments.slice(start, end);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={'comment__replies-container'}>
|
<li className="comment__replies-container">
|
||||||
<div className="comment__actions">
|
<div className="comment__actions">
|
||||||
<Button
|
<Button
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
|
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
|
||||||
className="comment__action"
|
className="comment__action"
|
||||||
onClick={() => setReplying(!isReplying)}
|
onClick={handleCommentReply}
|
||||||
icon={ICONS.REPLY}
|
icon={ICONS.REPLY}
|
||||||
/>
|
/>
|
||||||
{!isExpanded && Boolean(numberOfComments) && (
|
{!isExpanded && Boolean(numberOfComments) && (
|
||||||
|
@ -124,7 +140,7 @@ function CommentsReplies(props: Props) {
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
|
label={commentingEnabled ? __('Reply') : __('Log in to reply')}
|
||||||
className="comment__action--nested"
|
className="comment__action--nested"
|
||||||
onClick={() => setReplying(!isReplying)}
|
onClick={handleCommentReply}
|
||||||
icon={ICONS.REPLY}
|
icon={ICONS.REPLY}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -20,6 +20,7 @@ type Props = {
|
||||||
|
|
||||||
function InviteNew(props: Props) {
|
function InviteNew(props: Props) {
|
||||||
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
|
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
|
||||||
|
const noChannels = !channels || !(channels.length > 0);
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
|
@ -77,13 +78,15 @@ function InviteNew(props: Props) {
|
||||||
actions={
|
actions={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<CopyableText label={__('Your invite link')} copyable={referral} />
|
<CopyableText label={__('Your invite link')} copyable={referral} />
|
||||||
<SelectChannel
|
{!noChannels && (
|
||||||
channel={referralSource}
|
<SelectChannel
|
||||||
onChannelChange={channel => handleReferralChange(channel)}
|
channel={referralSource}
|
||||||
label={__('Customize link')}
|
onChannelChange={channel => handleReferralChange(channel)}
|
||||||
hideAnon
|
label={__('Customize link')}
|
||||||
injected={[referralCode]}
|
hideAnon
|
||||||
/>
|
injected={[referralCode]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<p className="help">
|
<p className="help">
|
||||||
<I18nMessage
|
<I18nMessage
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { selectBalance } from 'lbry-redux';
|
||||||
import ChannelNew from './view';
|
import ChannelNew from './view';
|
||||||
|
|
||||||
const select = () => ({});
|
const select = state => ({
|
||||||
const perform = () => ({});
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ChannelNew);
|
export default connect(select)(ChannelNew);
|
||||||
|
|
|
@ -2,20 +2,39 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ChannelEdit from 'component/channelEdit';
|
import ChannelEdit from 'component/channelEdit';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import { withRouter } from 'react-router';
|
import Button from 'component/button';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
history: { push: string => void, goBack: () => void },
|
balance: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelNew(props: Props) {
|
function ChannelNew(props: Props) {
|
||||||
const { history } = props;
|
const { balance } = props;
|
||||||
|
const { push, location } = useHistory();
|
||||||
|
const urlSearchParams = new URLSearchParams(location.search);
|
||||||
|
const redirectUrl = urlSearchParams.get('redirect');
|
||||||
|
const emptyBalance = balance === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page noSideNavigation authPage backout={{ title: __('Create Channel') }}>
|
<Page noSideNavigation noFooter backout={{ title: __('Create A Channel'), backLabel: __('Cancel') }}>
|
||||||
<ChannelEdit onDone={() => history.push(`/$/${PAGES.CHANNELS}`)} />
|
{emptyBalance && (
|
||||||
|
<div className="main--contained">
|
||||||
|
<div className="notice-message--above-content">
|
||||||
|
<h1 className="section__title">You need LBC for this</h1>
|
||||||
|
<h1 className="section__subtitle">Get sum coinz</h1>
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button button="primary" label={__('Earn Rewards')} navigate={`/$/${PAGES.REWARDS}`} />
|
||||||
|
<Button button="primary" label={__('Purchase LBC')} navigate={`/$/${PAGES.BUY}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ChannelEdit disabled={emptyBalance} onDone={() => push(redirectUrl || `/$/${PAGES.CHANNELS}`)} />
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(ChannelNew);
|
export default ChannelNew;
|
||||||
|
|
|
@ -4,6 +4,7 @@ $thumbnailWidthSmall: 2rem;
|
||||||
.comments {
|
.comments {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments--replies {
|
.comments--replies {
|
||||||
|
|
|
@ -301,6 +301,11 @@ textarea {
|
||||||
background-color: var(--color-primary-alt);
|
background-color: var(--color-primary-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notice-message--above-content {
|
||||||
|
@extend .notice-message;
|
||||||
|
margin-bottom: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
.privacy-img {
|
.privacy-img {
|
||||||
height: 10rem;
|
height: 10rem;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue