select tags before channels and filter channels by tag
moartags CS tags followed category continue button, Remove card header on tags select limitShow tags count tags limit fix debug cs tags highlighting bugfix yarnlock
This commit is contained in:
parent
f8357c4ec6
commit
d9e65e8328
22 changed files with 325 additions and 93 deletions
|
@ -131,7 +131,7 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#8245b055746216f7e1a12744fe6fbda3e3e90705",
|
||||
"lbry-redux": "lbryio/lbry-redux#6ed0dde5cbd7c25aa02631d5fa31fb6a4de76876",
|
||||
"lbryinc": "lbryio/lbryinc#275f35b31ec614e2b89689f860fe19e645deee68",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -1034,6 +1034,8 @@
|
|||
"Deleting or editing comments is not currently possible. Please be mindful of this when posting.": "Deleting or editing comments is not currently possible. Please be mindful of this when posting.",
|
||||
"When the alpha ends, we will attempt to transition comments, but do not promise to do so.": "When the alpha ends, we will attempt to transition comments, but do not promise to do so.",
|
||||
"More Channels": "More Channels",
|
||||
"Known Tags": "Known Tags",
|
||||
"More Channels": "More Channels",
|
||||
"You aren’t blocking any channels": "You aren’t blocking any channels",
|
||||
"When you block a channel, all content from that channel will be hidden.": "When you block a channel, all content from that channel will be hidden.",
|
||||
"View top claims for %normalized_uri%": "View top claims for %normalized_uri%",
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
selectUploadCount,
|
||||
selectUnclaimedRewards,
|
||||
doUserSetReferrer,
|
||||
selectUserVerifiedEmail,
|
||||
} from 'lbryinc';
|
||||
import { doFetchTransactions, doFetchChannelListMine } from 'lbry-redux';
|
||||
import { makeSelectClientSetting, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||
|
@ -35,6 +36,7 @@ const select = state => ({
|
|||
syncError: selectGetSyncErrorMessage(state),
|
||||
uploadCount: selectUploadCount(state),
|
||||
rewards: selectUnclaimedRewards(state),
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -50,9 +52,4 @@ const perform = dispatch => ({
|
|||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||
});
|
||||
|
||||
export default hot(
|
||||
connect(
|
||||
select,
|
||||
perform
|
||||
)(App)
|
||||
);
|
||||
export default hot(connect(select, perform)(App));
|
||||
|
|
|
@ -68,6 +68,7 @@ type Props = {
|
|||
rewards: Array<Reward>,
|
||||
setReferrer: (string, boolean) => void,
|
||||
analyticsTagSync: () => void,
|
||||
isAuthenticated: boolean,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
|
@ -93,6 +94,7 @@ function App(props: Props) {
|
|||
rewards,
|
||||
setReferrer,
|
||||
analyticsTagSync,
|
||||
isAuthenticated,
|
||||
} = props;
|
||||
|
||||
const appRef = useRef();
|
||||
|
@ -242,11 +244,11 @@ function App(props: Props) {
|
|||
}, [hasVerifiedEmail, syncEnabled, checkSync]);
|
||||
|
||||
useEffect(() => {
|
||||
if (syncError) {
|
||||
if (syncError && isAuthenticated) {
|
||||
history.push(`/$/${PAGES.AUTH}?redirect=${pathname}`);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [syncError, pathname]);
|
||||
}, [syncError, pathname, isAuthenticated]);
|
||||
|
||||
// @if TARGET='web'
|
||||
useEffect(() => {
|
||||
|
|
|
@ -5,12 +5,14 @@ import {
|
|||
selectFetchingClaimSearch,
|
||||
selectBlockedChannels,
|
||||
SETTINGS,
|
||||
selectFollowedTags,
|
||||
} from 'lbry-redux';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import ClaimListDiscover from './view';
|
||||
|
||||
const select = state => ({
|
||||
followedTags: selectFollowedTags(state),
|
||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||
loading: selectFetchingClaimSearch(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||
|
|
|
@ -32,7 +32,8 @@ type Props = {
|
|||
hiddenUris: Array<string>,
|
||||
hiddenNsfwMessage?: Node,
|
||||
channelIds?: Array<string>,
|
||||
tags: Array<string>,
|
||||
tags: string, // these are just going to be string. pass a CSV if you want multi
|
||||
defaultTags: string,
|
||||
orderBy?: Array<string>,
|
||||
defaultOrderBy?: string,
|
||||
freshness?: string,
|
||||
|
@ -49,6 +50,7 @@ type Props = {
|
|||
renderProperties?: Claim => Node,
|
||||
includeSupportAction?: boolean,
|
||||
pageSize?: number,
|
||||
followedTags?: Array<Tag>,
|
||||
};
|
||||
|
||||
function ClaimListDiscover(props: Props) {
|
||||
|
@ -56,11 +58,12 @@ function ClaimListDiscover(props: Props) {
|
|||
doClaimSearch,
|
||||
claimSearchByQuery,
|
||||
tags,
|
||||
defaultTags,
|
||||
loading,
|
||||
meta,
|
||||
channelIds,
|
||||
showNsfw,
|
||||
showReposts,
|
||||
// showReposts,
|
||||
history,
|
||||
location,
|
||||
hiddenUris,
|
||||
|
@ -81,6 +84,7 @@ function ClaimListDiscover(props: Props) {
|
|||
renderProperties,
|
||||
includeSupportAction,
|
||||
hideFilter,
|
||||
followedTags,
|
||||
} = props;
|
||||
const didNavigateForward = history.action === 'PUSH';
|
||||
const { search } = location;
|
||||
|
@ -88,9 +92,12 @@ function ClaimListDiscover(props: Props) {
|
|||
const [page, setPage] = useState(1);
|
||||
const [forceRefresh, setForceRefresh] = useState();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const followed = (followedTags && followedTags.map(t => t.name)) || [];
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const tagsParam = tags || urlParams.get(CS.TAGS_KEY) || null;
|
||||
const tagsParam = // can be 'x,y,z' or 'x' or ['x','y'] or CS.CONSTANT
|
||||
(tags && getParamFromTags(tags)) ||
|
||||
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
|
||||
(defaultTags && getParamFromTags(defaultTags));
|
||||
const orderParam = orderBy || urlParams.get(CS.ORDER_BY_KEY) || defaultOrderBy || CS.ORDER_BY_TRENDING;
|
||||
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
||||
const contentTypeParam = urlParams.get(CS.CONTENT_KEY);
|
||||
|
@ -102,7 +109,12 @@ function ClaimListDiscover(props: Props) {
|
|||
|
||||
const showDuration = !(claimType && claimType === CS.CLAIM_CHANNEL);
|
||||
const isFiltered = () =>
|
||||
Boolean(urlParams.get(CS.FRESH_KEY) || urlParams.get(CS.CONTENT_KEY) || urlParams.get(CS.DURATION_KEY));
|
||||
Boolean(
|
||||
urlParams.get(CS.FRESH_KEY) ||
|
||||
urlParams.get(CS.CONTENT_KEY) ||
|
||||
urlParams.get(CS.DURATION_KEY) ||
|
||||
urlParams.get(CS.TAGS_KEY)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFiltered()) setExpanded(true);
|
||||
|
@ -113,7 +125,7 @@ function ClaimListDiscover(props: Props) {
|
|||
page_size: number,
|
||||
page: number,
|
||||
no_totals: boolean,
|
||||
any_tags: Array<string>,
|
||||
any_tags?: Array<string>,
|
||||
not_tags: Array<string>,
|
||||
channel_ids: Array<string>,
|
||||
not_channel_ids: Array<string>,
|
||||
|
@ -131,7 +143,6 @@ function ClaimListDiscover(props: Props) {
|
|||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||
// it's faster, but we will need to remove it if we start using total_pages
|
||||
no_totals: true,
|
||||
any_tags: tagsParam || [],
|
||||
channel_ids: channelIds || [],
|
||||
not_channel_ids:
|
||||
// If channelIds were passed in, we don't need not_channel_ids
|
||||
|
@ -211,6 +222,17 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
if (tagsParam) {
|
||||
if (tagsParam !== CS.TAGS_ALL && tagsParam !== '') {
|
||||
if (tagsParam === CS.TAGS_FOLLOWED) {
|
||||
options.any_tags = followed;
|
||||
} else if (Array.isArray(tagsParam)) {
|
||||
options.any_tags = tagsParam;
|
||||
} else {
|
||||
options.any_tags = tagsParam.split(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://github.com/lbryio/lbry-desktop/issues/3774
|
||||
// if (!showReposts) {
|
||||
// if (Array.isArray(options.claim_type)) {
|
||||
|
@ -220,7 +242,7 @@ function ClaimListDiscover(props: Props) {
|
|||
// }
|
||||
// }
|
||||
|
||||
const hasMatureTags = tags && tags.some(t => MATURE_TAGS.includes(t));
|
||||
const hasMatureTags = tagsParam && tagsParam.split(',').some(t => MATURE_TAGS.includes(t));
|
||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
|
||||
const shouldPerformSearch =
|
||||
|
@ -265,6 +287,14 @@ function ClaimListDiscover(props: Props) {
|
|||
history.push(url);
|
||||
}
|
||||
|
||||
function getParamFromTags(t) {
|
||||
if (t === CS.TAGS_ALL || t === CS.TAGS_FOLLOWED) {
|
||||
return t;
|
||||
} else if (Array.isArray(t)) {
|
||||
return t.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
function buildUrl(delta) {
|
||||
const newUrlParams = new URLSearchParams();
|
||||
CS.KEYS.forEach(k => {
|
||||
|
@ -300,6 +330,23 @@ function ClaimListDiscover(props: Props) {
|
|||
newUrlParams.set(CS.DURATION_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.TAGS_KEY:
|
||||
if (delta.value === CS.TAGS_ALL) {
|
||||
if (defaultTags === CS.TAGS_ALL) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
} else if (delta.value === CS.TAGS_FOLLOWED) {
|
||||
if (defaultTags === CS.TAGS_FOLLOWED) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value); // redundant but special
|
||||
}
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return `?${newUrlParams.toString()}`;
|
||||
}
|
||||
|
@ -393,46 +440,48 @@ function ClaimListDiscover(props: Props) {
|
|||
)}
|
||||
|
||||
{/* CONTENT_TYPES FIELD */}
|
||||
<div
|
||||
className={classnames('claim-search__input-container', {
|
||||
'claim-search__input-container--selected': contentTypeParam,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': contentTypeParam,
|
||||
{!claimType && (
|
||||
<div
|
||||
className={classnames('claim-search__input-container', {
|
||||
'claim-search__input-container--selected': contentTypeParam,
|
||||
})}
|
||||
type="select"
|
||||
name="claimType"
|
||||
label={__('Content Type')}
|
||||
value={contentTypeParam || CS.CONTENT_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.CONTENT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.CONTENT_TYPES.map(type => {
|
||||
if (type !== CS.CLAIM_CHANNEL || (type === CS.CLAIM_CHANNEL && !channelIds)) {
|
||||
return (
|
||||
<option key={type} value={type}>
|
||||
{/* i18fixme */}
|
||||
{type === CS.CLAIM_CHANNEL && __('Channel')}
|
||||
{type === CS.CLAIM_REPOST && __('Repost')}
|
||||
{type === CS.FILE_VIDEO && __('Video')}
|
||||
{type === CS.FILE_AUDIO && __('Audio')}
|
||||
{type === CS.FILE_IMAGE && __('Image')}
|
||||
{type === CS.FILE_MODEL && __('Model')}
|
||||
{type === CS.FILE_BINARY && __('Other')}
|
||||
{type === CS.FILE_DOCUMENT && __('Document')}
|
||||
{type === CS.CONTENT_ALL && __('Any')}
|
||||
</option>
|
||||
);
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': contentTypeParam,
|
||||
})}
|
||||
type="select"
|
||||
name="claimType"
|
||||
label={__('Content Type')}
|
||||
value={contentTypeParam || CS.CONTENT_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.CONTENT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
})}
|
||||
</FormField>
|
||||
</div>
|
||||
>
|
||||
{CS.CONTENT_TYPES.map(type => {
|
||||
if (type !== CS.CLAIM_CHANNEL || (type === CS.CLAIM_CHANNEL && !channelIds)) {
|
||||
return (
|
||||
<option key={type} value={type}>
|
||||
{/* i18fixme */}
|
||||
{type === CS.CLAIM_CHANNEL && __('Channel')}
|
||||
{type === CS.CLAIM_REPOST && __('Repost')}
|
||||
{type === CS.FILE_VIDEO && __('Video')}
|
||||
{type === CS.FILE_AUDIO && __('Audio')}
|
||||
{type === CS.FILE_IMAGE && __('Image')}
|
||||
{type === CS.FILE_MODEL && __('Model')}
|
||||
{type === CS.FILE_BINARY && __('Other')}
|
||||
{type === CS.FILE_DOCUMENT && __('Document')}
|
||||
{type === CS.CONTENT_ALL && __('Any')}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
{/* DURATIONS FIELD */}
|
||||
{showDuration && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
|
@ -469,6 +518,50 @@ function ClaimListDiscover(props: Props) {
|
|||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
{/* TAGS FIELD */}
|
||||
{!tags && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected':
|
||||
((!defaultTags || defaultTags === CS.TAGS_ALL) && tagsParam && tagsParam !== CS.TAGS_ALL) ||
|
||||
(defaultTags === CS.TAGS_FOLLOWED && tagsParam !== CS.TAGS_FOLLOWED),
|
||||
})}
|
||||
label={__('Tags')}
|
||||
type="select"
|
||||
name="tags"
|
||||
value={tagsParam || CS.TAGS_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.TAGS_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{[
|
||||
CS.TAGS_ALL,
|
||||
CS.TAGS_FOLLOWED,
|
||||
...followed,
|
||||
...(followed.includes(tagsParam) || tagsParam === CS.TAGS_ALL || tagsParam === CS.TAGS_FOLLOWED
|
||||
? []
|
||||
: [tagsParam]), // if they unfollow while filtered, add Other
|
||||
].map(tag => (
|
||||
<option
|
||||
key={tag}
|
||||
value={tag}
|
||||
className={classnames({
|
||||
'claim-search__input-special': !followed.includes(tag),
|
||||
})}
|
||||
>
|
||||
{followed.includes(tag) && typeof tag === 'string' && toCapitalCase(__(tag))}
|
||||
{tag === CS.TAGS_ALL && __('Any')}
|
||||
{tag === CS.TAGS_FOLLOWED && __('Following')}
|
||||
{!followed.includes(tag) && tag !== CS.TAGS_ALL && tag !== CS.TAGS_FOLLOWED && __('Other')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -6,7 +6,6 @@ import ClaimPreviewTile from 'component/claimPreviewTile';
|
|||
type Props = {
|
||||
uris: Array<string>,
|
||||
doClaimSearch: ({}) => void,
|
||||
loading: boolean,
|
||||
showNsfw: boolean,
|
||||
showReposts: boolean,
|
||||
history: { action: string, push: string => void, replace: string => void },
|
||||
|
@ -29,9 +28,8 @@ function ClaimTilesDiscover(props: Props) {
|
|||
const {
|
||||
doClaimSearch,
|
||||
claimSearchByQuery,
|
||||
loading,
|
||||
showNsfw,
|
||||
showReposts,
|
||||
// showReposts,
|
||||
hiddenUris,
|
||||
// Below are options to pass that are forwarded to claim_search
|
||||
tags,
|
||||
|
@ -95,7 +93,7 @@ function ClaimTilesDiscover(props: Props) {
|
|||
|
||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
|
||||
const shouldPerformSearch = !hasSearched || uris.length === 0 || (!loading && uris.length < pageSize);
|
||||
const shouldPerformSearch = !hasSearched || uris.length === 0;
|
||||
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
|
||||
const optionsStringForEffect = JSON.stringify(options);
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ function PublishForm(props: Props) {
|
|||
hideHeader
|
||||
label={__('Selected Tags')}
|
||||
empty={__('No tags added')}
|
||||
limit={TAGS_LIMIT}
|
||||
limitSelect={TAGS_LIMIT}
|
||||
help={__(
|
||||
'Add tags that are relevant to your content. If mature content, ensure it is tagged mature. Tag abuse and missing mature tags will not be tolerated.'
|
||||
)}
|
||||
|
|
|
@ -18,7 +18,8 @@ type Props = {
|
|||
placeholder?: string,
|
||||
label?: string,
|
||||
disabled?: boolean,
|
||||
limit?: number,
|
||||
limitSelect?: number,
|
||||
limitShow?: number,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -42,7 +43,8 @@ export default function TagsSearch(props: Props) {
|
|||
placeholder,
|
||||
label,
|
||||
disabled,
|
||||
limit,
|
||||
limitSelect,
|
||||
limitShow = 5,
|
||||
} = props;
|
||||
const [newTag, setNewTag] = useState('');
|
||||
const doesTagMatch = name => {
|
||||
|
@ -60,10 +62,10 @@ export default function TagsSearch(props: Props) {
|
|||
const suggestedTagsSet = setUnion(remainingFollowedTagsSet, unfollowedTagsSet);
|
||||
|
||||
const countWithoutMature = selectedTagsSet.has('mature') ? selectedTagsSet.size - 1 : selectedTagsSet.size;
|
||||
const maxed = Boolean(limit && countWithoutMature >= limit);
|
||||
const maxed = Boolean(limitSelect && countWithoutMature >= limitSelect);
|
||||
const suggestedTags = Array.from(suggestedTagsSet)
|
||||
.filter(doesTagMatch)
|
||||
.slice(0, 5);
|
||||
.slice(0, limitShow);
|
||||
|
||||
// tack 'mature' onto the end if it's not already in the list
|
||||
if (!newTag && suggestMature && !suggestedTags.some(tag => tag === 'mature')) {
|
||||
|
@ -116,7 +118,7 @@ export default function TagsSearch(props: Props) {
|
|||
<React.Fragment>
|
||||
<Form className="tags__input-wrapper" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
{limit ? (
|
||||
{limitSelect ? (
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
number: 5 - countWithoutMature,
|
||||
|
|
|
@ -24,7 +24,8 @@ type Props = {
|
|||
placeholder?: string,
|
||||
disableAutoFocus?: boolean,
|
||||
hideHeader?: boolean,
|
||||
limit?: number,
|
||||
limitShow?: number,
|
||||
limitSelect?: number,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -45,7 +46,8 @@ export default function TagsSelect(props: Props) {
|
|||
placeholder,
|
||||
hideHeader,
|
||||
label,
|
||||
limit,
|
||||
limitShow,
|
||||
limitSelect,
|
||||
} = props;
|
||||
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
||||
const tagsToDisplay = tagsChosen || followedTags;
|
||||
|
@ -107,7 +109,8 @@ export default function TagsSelect(props: Props) {
|
|||
disableAutoFocus={disableAutoFocus}
|
||||
tagsPassedIn={tagsToDisplay}
|
||||
placeholder={placeholder}
|
||||
limit={limit}
|
||||
limitShow={limitShow}
|
||||
limitSelect={limitSelect}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFollowedTags } from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { doChannelSubscribe } from 'redux/actions/subscriptions';
|
||||
import UserChannelFollowIntro from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -8,4 +9,11 @@ const select = state => ({
|
|||
subscribedChannels: selectSubscriptions(state),
|
||||
});
|
||||
|
||||
export default connect(select)(UserChannelFollowIntro);
|
||||
const perform = dispatch => ({
|
||||
channelSubscribe: uri => dispatch(doChannelSubscribe(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UserChannelFollowIntro);
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ClaimListDiscover from 'component/claimListDiscover';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import Nag from 'component/common/nag';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import Button from 'component/button';
|
||||
import { Form } from 'component/common/form-components/form';
|
||||
|
||||
type Props = {
|
||||
subscribedChannels: Array<Subscription>,
|
||||
onContinue: () => void,
|
||||
onBack: () => void,
|
||||
channelSubscribe: (sub: Subscription) => void,
|
||||
};
|
||||
|
||||
const LBRYURI = 'lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a';
|
||||
function UserChannelFollowIntro(props: Props) {
|
||||
const { subscribedChannels, onContinue } = props;
|
||||
const { subscribedChannels, channelSubscribe, onContinue, onBack } = props;
|
||||
const followingCount = (subscribedChannels && subscribedChannels.length) || 0;
|
||||
|
||||
// subscribe to lbry
|
||||
useEffect(() => {
|
||||
channelSubscribe({
|
||||
channelName: parseURI(LBRYURI).claimName,
|
||||
uri: LBRYURI,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="section__title--large">{__('Find Channels to Follow')}</h1>
|
||||
|
@ -21,13 +35,25 @@ function UserChannelFollowIntro(props: Props) {
|
|||
'LBRY works better if you find and follow at least 5 creators you like. You can also block channels you never want to see.'
|
||||
)}
|
||||
</p>
|
||||
<Form onSubmit={onContinue} className="section__body">
|
||||
<div className="card__actions">
|
||||
<Button button="secondary" onClick={onBack} label={__('Back')} />
|
||||
<Button
|
||||
button="primary"
|
||||
type="Submit"
|
||||
onClick={onContinue}
|
||||
label={__('Continue')}
|
||||
disabled={subscribedChannels.length < 2}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
<div className="section__body">
|
||||
<ClaimListDiscover
|
||||
defaultOrderBy={CS.ORDER_BY_TOP}
|
||||
defaultFreshness={CS.FRESH_ALL}
|
||||
claimType="channel"
|
||||
hideBlock
|
||||
hideFilter
|
||||
defaultTags={CS.TAGS_FOLLOWED}
|
||||
/>
|
||||
{followingCount > 0 && (
|
||||
<Nag
|
||||
|
|
|
@ -6,6 +6,7 @@ import UserEmailNew from 'component/userEmailNew';
|
|||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import UserFirstChannel from 'component/userFirstChannel';
|
||||
import UserChannelFollowIntro from 'component/userChannelFollowIntro';
|
||||
import UserTagFollowIntro from 'component/userTagFollowIntro';
|
||||
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
|
||||
import { rewards as REWARDS, YOUTUBE_STATUSES } from 'lbryinc';
|
||||
import UserVerify from 'component/userVerify';
|
||||
|
@ -59,10 +60,12 @@ function UserSignIn(props: Props) {
|
|||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const redirect = urlParams.get('redirect');
|
||||
const step = urlParams.get('step');
|
||||
const shouldRedirectImmediately = urlParams.get('immediate');
|
||||
const [initialSignInStep, setInitialSignInStep] = React.useState();
|
||||
const [hasSeenFollowList, setHasSeenFollowList] = usePersistedState('channel-follow-intro', false);
|
||||
const [hasSkippedRewards, setHasSkippedRewards] = usePersistedState('skip-rewards-intro', false);
|
||||
const [hasSeenTagsList, setHasSeenTagsList] = usePersistedState('channel-follow-intro', false);
|
||||
const hasVerifiedEmail = user && user.has_verified_email;
|
||||
const rewardsApproved = user && user.is_reward_approved;
|
||||
const isIdentityVerified = user && user.is_identity_verified;
|
||||
|
@ -92,7 +95,8 @@ function UserSignIn(props: Props) {
|
|||
channelCount === 0 &&
|
||||
!hasYoutubeChannels;
|
||||
const showYoutubeTransfer = hasVerifiedEmail && hasYoutubeChannels && !isYoutubeTransferComplete;
|
||||
const showFollowIntro = hasVerifiedEmail && !hasSeenFollowList;
|
||||
const showFollowIntro = step === 'channels' || (hasVerifiedEmail && !hasSeenFollowList);
|
||||
const showTagsIntro = step === 'tags' || (hasVerifiedEmail && !hasSeenTagsList);
|
||||
const canHijackSignInFlowWithSpinner = hasVerifiedEmail && !getSyncError && !showFollowIntro;
|
||||
const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet || creatingChannel;
|
||||
const isWaitingForSomethingToFinish =
|
||||
|
@ -136,6 +140,34 @@ function UserSignIn(props: Props) {
|
|||
history.replace(url);
|
||||
setHasSeenFollowList(true);
|
||||
}}
|
||||
onBack={() => {
|
||||
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=tags`;
|
||||
if (redirect) {
|
||||
url += `&redirect=${redirect}`;
|
||||
}
|
||||
if (shouldRedirectImmediately) {
|
||||
url += `&immediate=true`;
|
||||
}
|
||||
|
||||
history.replace(url);
|
||||
setHasSeenFollowList(false);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
showTagsIntro && (
|
||||
<UserTagFollowIntro
|
||||
onContinue={() => {
|
||||
let url = `/$/${PAGES.AUTH}?reset_scroll=1&step=channels`;
|
||||
if (redirect) {
|
||||
url += `&redirect=${redirect}`;
|
||||
}
|
||||
if (shouldRedirectImmediately) {
|
||||
url += `&immediate=true`;
|
||||
}
|
||||
|
||||
history.replace(url);
|
||||
setHasSeenTagsList(true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
showYoutubeTransfer && (
|
||||
|
|
9
ui/component/userTagFollowIntro/index.js
Normal file
9
ui/component/userTagFollowIntro/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFollowedTags } from 'lbry-redux';
|
||||
import UserTagFollowIntro from './view';
|
||||
|
||||
const select = state => ({
|
||||
followedTags: selectFollowedTags(state),
|
||||
});
|
||||
|
||||
export default connect(select)(UserTagFollowIntro);
|
52
ui/component/userTagFollowIntro/view.jsx
Normal file
52
ui/component/userTagFollowIntro/view.jsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Nag from 'component/common/nag';
|
||||
import TagsSelect from 'component/tagsSelect';
|
||||
import Button from 'component/button';
|
||||
import { Form } from 'component/common/form';
|
||||
|
||||
type Props = {
|
||||
subscribedChannels: Array<Subscription>,
|
||||
onContinue: () => void,
|
||||
followedTags: Array<Tag>,
|
||||
};
|
||||
|
||||
function UserChannelFollowIntro(props: Props) {
|
||||
const { onContinue, followedTags } = props;
|
||||
const followingCount = (followedTags && followedTags.length) || 0;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="section__title--large">{__('Tag Selection')}</h1>
|
||||
<p className="section__subtitle">{__('Select some tags to help us show you interesting things.')}</p>
|
||||
<Form onSubmit={onContinue} className="section__body">
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
type="Submit"
|
||||
onClick={onContinue}
|
||||
label={__('Continue')}
|
||||
disabled={followedTags.length < 1}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
<div className="section__body">
|
||||
<TagsSelect hideHeader limitShow={300} help={false} showClose={false} title={__('Follow New Tags')} />
|
||||
{followingCount > 0 && (
|
||||
<Nag
|
||||
type="helpful"
|
||||
message={
|
||||
followingCount === 1
|
||||
? __('You are currently following %followingCount% tag', { followingCount })
|
||||
: __('You are currently following %followingCount% tags', { followingCount })
|
||||
}
|
||||
actionText={__('Continue')}
|
||||
onClick={onContinue}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserChannelFollowIntro;
|
|
@ -6,6 +6,9 @@ export const DURATION_KEY = 'duration';
|
|||
export const TAGS_KEY = 't';
|
||||
export const CONTENT_KEY = 'content';
|
||||
|
||||
export const TAGS_ALL = 'tags_any';
|
||||
export const TAGS_FOLLOWED = 'tags_followed';
|
||||
|
||||
export const FRESH_DAY = 'day';
|
||||
export const FRESH_WEEK = 'week';
|
||||
export const FRESH_MONTH = 'month';
|
||||
|
|
|
@ -119,12 +119,7 @@ function ChannelsFollowingDiscover(props: Props) {
|
|||
</div>
|
||||
))}
|
||||
<h1 className="claim-grid__title">{__('More Channels')}</h1>
|
||||
<ClaimListDiscover
|
||||
defaultOrderBy={CS.ORDER_BY_TOP}
|
||||
defaultFreshness={CS.FRESH_ALL}
|
||||
claimType="channel"
|
||||
hideFilter
|
||||
/>
|
||||
<ClaimListDiscover defaultOrderBy={CS.ORDER_BY_TOP} defaultFreshness={CS.FRESH_ALL} claimType="channel" />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import useHover from 'effects/use-hover';
|
|||
import analytics from 'analytics';
|
||||
import HiddenNsfw from 'component/common/hidden-nsfw';
|
||||
import Icon from 'component/common/icon';
|
||||
import * as CS from 'constants/claim_search';
|
||||
|
||||
type Props = {
|
||||
location: { search: string },
|
||||
|
@ -26,11 +27,11 @@ function TagsPage(props: Props) {
|
|||
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const claimType = urlParams.get('claim_type');
|
||||
const tagsQuery = urlParams.get('t') || '';
|
||||
const tags = tagsQuery.split(',');
|
||||
const tagsQuery = urlParams.get('t') || null;
|
||||
const tags = tagsQuery ? tagsQuery.split(',') : null;
|
||||
// Eventually allow more than one tag on this page
|
||||
// Restricting to one to make follow/unfollow simpler
|
||||
const tag = tags[0];
|
||||
const tag = (tags && tags[0]) || null;
|
||||
|
||||
const isFollowing = followedTags.map(({ name }) => name).includes(tag);
|
||||
let label = isFollowing ? __('Following') : __('Follow');
|
||||
|
@ -39,10 +40,12 @@ function TagsPage(props: Props) {
|
|||
}
|
||||
|
||||
function handleFollowClick() {
|
||||
doToggleTagFollowDesktop(tag);
|
||||
if (tag) {
|
||||
doToggleTagFollowDesktop(tag);
|
||||
|
||||
const nowFollowing = !isFollowing;
|
||||
analytics.tagFollowEvent(tag, nowFollowing, 'tag-page');
|
||||
const nowFollowing = !isFollowing;
|
||||
analytics.tagFollowEvent(tag, nowFollowing, 'tag-page');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -53,7 +56,9 @@ function TagsPage(props: Props) {
|
|||
tag ? (
|
||||
<span>
|
||||
<Icon icon={ICONS.TAG} size={10} />
|
||||
{tag}
|
||||
{(tag === CS.TAGS_ALL && __('All Content')) ||
|
||||
(tag === CS.TAGS_FOLLOWED && __('Followed Tags')) ||
|
||||
__(tag)}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
|
@ -62,7 +67,7 @@ function TagsPage(props: Props) {
|
|||
</span>
|
||||
)
|
||||
}
|
||||
tags={tags}
|
||||
defaultTags={CS.TAGS_ALL}
|
||||
hiddenNsfwMessage={<HiddenNsfw type="page" />}
|
||||
meta={
|
||||
tag && (
|
||||
|
|
|
@ -7,18 +7,18 @@ import TagsSelect from 'component/tagsSelect';
|
|||
import Page from 'component/page';
|
||||
import Button from 'component/button';
|
||||
import Icon from 'component/common/icon';
|
||||
import * as CS from 'constants/claim_search';
|
||||
|
||||
type Props = {
|
||||
followedTags: Array<Tag>,
|
||||
email: string,
|
||||
};
|
||||
|
||||
function DiscoverPage(props: Props) {
|
||||
const { followedTags, email } = props;
|
||||
const { email } = props;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{(email || !IS_WEB) && <TagsSelect showClose title={__('Find New Tags To Follow')} />}
|
||||
{(email || !IS_WEB) && <TagsSelect showClose limitShow={300} title={__('Find New Tags To Follow')} />}
|
||||
<ClaimListDiscover
|
||||
headerLabel={
|
||||
<span>
|
||||
|
@ -28,7 +28,7 @@ function DiscoverPage(props: Props) {
|
|||
}
|
||||
hideCustomization={IS_WEB && !email}
|
||||
personalView
|
||||
tags={followedTags.map(tag => tag.name)}
|
||||
defaultTags={CS.TAGS_FOLLOWED}
|
||||
meta={
|
||||
<Button
|
||||
button="link"
|
||||
|
|
|
@ -6,7 +6,7 @@ import TagsSelect from 'component/tagsSelect';
|
|||
function FollowingPage() {
|
||||
return (
|
||||
<Page>
|
||||
<TagsSelect showClose={false} title={__('Follow New Tags')} />
|
||||
<TagsSelect limitShow={300} showClose={false} title={__('Follow New Tags')} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-search__input-special {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.claim-search__extra {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -7081,9 +7081,9 @@ lazy-val@^1.0.4:
|
|||
yargs "^13.2.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#8245b055746216f7e1a12744fe6fbda3e3e90705:
|
||||
lbry-redux@lbryio/lbry-redux#6ed0dde5cbd7c25aa02631d5fa31fb6a4de76876:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/8245b055746216f7e1a12744fe6fbda3e3e90705"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6ed0dde5cbd7c25aa02631d5fa31fb6a4de76876"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue