Skip to content

Commit

Permalink
Change onboarding flow in web UI (mastodon#32998)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron authored Nov 26, 2024
1 parent 429e08e commit 7a3dea3
Show file tree
Hide file tree
Showing 32 changed files with 1,142 additions and 1,183 deletions.
58 changes: 0 additions & 58 deletions app/javascript/mastodon/actions/suggestions.js

This file was deleted.

24 changes: 24 additions & 0 deletions app/javascript/mastodon/actions/suggestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
apiGetSuggestions,
apiDeleteSuggestion,
} from 'mastodon/api/suggestions';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';

import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';

export const fetchSuggestions = createDataLoadingThunk(
'suggestions/fetch',
() => apiGetSuggestions(20),
(data, { dispatch }) => {
dispatch(importFetchedAccounts(data.map((x) => x.account)));
dispatch(fetchRelationships(data.map((x) => x.account.id)));

return data;
},
);

export const dismissSuggestion = createDataLoadingThunk(
'suggestions/dismiss',
({ accountId }: { accountId: string }) => apiDeleteSuggestion(accountId),
);
8 changes: 8 additions & 0 deletions app/javascript/mastodon/api/suggestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { apiRequestGet, apiRequestDelete } from 'mastodon/api';
import type { ApiSuggestionJSON } from 'mastodon/api_types/suggestions';

export const apiGetSuggestions = (limit: number) =>
apiRequestGet<ApiSuggestionJSON[]>('v2/suggestions', { limit });

export const apiDeleteSuggestion = (accountId: string) =>
apiRequestDelete(`v1/suggestions/${accountId}`);
13 changes: 13 additions & 0 deletions app/javascript/mastodon/api_types/suggestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';

export type ApiSuggestionSourceJSON =
| 'featured'
| 'most_followed'
| 'most_interactions'
| 'similar_to_recently_followed'
| 'friends_of_friends';

export interface ApiSuggestionJSON {
sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
account: ApiAccountJSON;
}
20 changes: 7 additions & 13 deletions app/javascript/mastodon/components/account.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';

import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { EmptyAccount } from 'mastodon/components/empty_account';
import { FollowButton } from 'mastodon/components/follow_button';
import { ShortNumber } from 'mastodon/components/short_number';
import { VerifiedBadge } from 'mastodon/components/verified_badge';

Expand All @@ -23,9 +24,6 @@ import { DisplayName } from './display_name';
import { RelativeTimestamp } from './relative_timestamp';

const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
Expand All @@ -35,13 +33,9 @@ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' },
});

const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
const intl = useIntl();

const handleFollow = useCallback(() => {
onFollow(account);
}, [onFollow, account]);

const handleBlock = useCallback(() => {
onBlock(account);
}, [onBlock, account]);
Expand Down Expand Up @@ -74,13 +68,12 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
let buttons;

if (account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
const muting = account.getIn(['relationship', 'muting']);

if (requested) {
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={handleFollow} />;
buttons = <FollowButton accountId={account.get('id')} />;
} else if (blocking) {
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
} else if (muting) {
Expand Down Expand Up @@ -109,9 +102,11 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
} else if (defaultAction === 'block') {
buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
} else if (!account.get('suspended') && !account.get('moved') || following) {
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={handleFollow} />;
} else {
buttons = <FollowButton accountId={account.get('id')} />;
}
} else {
buttons = <FollowButton accountId={account.get('id')} />;
}

let muteTimeRemaining;
Expand Down Expand Up @@ -168,7 +163,6 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
Account.propTypes = {
size: PropTypes.number,
account: ImmutablePropTypes.record,
onFollow: PropTypes.func,
onBlock: PropTypes.func,
onMute: PropTypes.func,
onMuteNotifications: PropTypes.func,
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/column_back_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function useHandleClick(onClick?: OnClickCallback) {
}, [history, onClick]);
}

export const ColumnBackButton: React.FC<{ onClick: OnClickCallback }> = ({
export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({
onClick,
}) => {
const handleClick = useHandleClick(onClick);
Expand Down
67 changes: 67 additions & 0 deletions app/javascript/mastodon/components/column_search_header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useCallback, useState, useEffect, useRef } from 'react';

import { FormattedMessage } from 'react-intl';

export const ColumnSearchHeader: React.FC<{
onBack: () => void;
onSubmit: (value: string) => void;
onActivate: () => void;
placeholder: string;
active: boolean;
}> = ({ onBack, onActivate, onSubmit, placeholder, active }) => {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState('');

useEffect(() => {
if (!active) {
setValue('');
}
}, [active]);

const handleChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
setValue(value);
onSubmit(value);
},
[setValue, onSubmit],
);

const handleKeyUp = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape') {
e.preventDefault();
onBack();
inputRef.current?.blur();
}
},
[onBack],
);

const handleFocus = useCallback(() => {
onActivate();
}, [onActivate]);

const handleSubmit = useCallback(() => {
onSubmit(value);
}, [onSubmit, value]);

return (
<form className='column-search-header' onSubmit={handleSubmit}>
<input
ref={inputRef}
type='search'
value={value}
onChange={handleChange}
onKeyUp={handleKeyUp}
placeholder={placeholder}
onFocus={handleFocus}
/>

{active && (
<button type='button' className='link-button' onClick={onBack}>
<FormattedMessage id='column_search.cancel' defaultMessage='Cancel' />
</button>
)}
</form>
);
};
7 changes: 6 additions & 1 deletion app/javascript/mastodon/components/follow_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ export const FollowButton: React.FC<{
return (
<Button
onClick={handleClick}
disabled={relationship?.blocked_by || relationship?.blocking}
disabled={
relationship?.blocked_by ||
relationship?.blocking ||
(!(relationship?.following || relationship?.requested) &&
(account?.suspended || !!account?.moved))
}
secondary={following}
className={following ? 'button--destructive' : undefined}
>
Expand Down
19 changes: 9 additions & 10 deletions app/javascript/mastodon/features/explore/suggestions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl';

import { withRouter } from 'react-router-dom';

import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';

import { fetchSuggestions } from 'mastodon/actions/suggestions';
Expand All @@ -15,15 +14,15 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { Card } from './components/card';

const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
isLoading: state.getIn(['suggestions', 'isLoading']),
suggestions: state.suggestions.items,
isLoading: state.suggestions.isLoading,
});

class Suggestions extends PureComponent {

static propTypes = {
isLoading: PropTypes.bool,
suggestions: ImmutablePropTypes.list,
suggestions: PropTypes.array,
dispatch: PropTypes.func.isRequired,
...WithRouterPropTypes,
};
Expand All @@ -32,17 +31,17 @@ class Suggestions extends PureComponent {
const { dispatch, suggestions, history } = this.props;

// If we're navigating back to the screen, do not trigger a reload
if (history.action === 'POP' && suggestions.size > 0) {
if (history.action === 'POP' && suggestions.length > 0) {
return;
}

dispatch(fetchSuggestions(true));
dispatch(fetchSuggestions());
}

render () {
const { isLoading, suggestions } = this.props;

if (!isLoading && suggestions.isEmpty()) {
if (!isLoading && suggestions.length === 0) {
return (
<div className='explore__suggestions scrollable scrollable--flex'>
<div className='empty-column-indicator'>
Expand All @@ -56,9 +55,9 @@ class Suggestions extends PureComponent {
<div className='explore__suggestions scrollable' data-nosnippet>
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
<Card
key={suggestion.get('account')}
id={suggestion.get('account')}
source={suggestion.getIn(['sources', 0])}
key={suggestion.account_id}
id={suggestion.account_id}
source={suggestion.sources[0]}
/>
))}
</div>
Expand Down
Loading

0 comments on commit 7a3dea3

Please sign in to comment.