Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add profile setup to onboarding in web UI #2570

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/javascript/flavours/glitch/actions/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,21 @@ export function fetchPinnedAccountsFail(error) {
};
}

export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => {
const data = new FormData();

data.append('display_name', displayName);
data.append('note', note);
if (avatar) data.append('avatar', avatar);
if (header) data.append('header', header);
data.append('discoverable', discoverable);
data.append('indexable', indexable);

return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => {
dispatch(importFetchedAccount(response.data));
});
};

export function fetchPinnedAccountsSuggestions(q) {
return (dispatch, getState) => {
dispatch(fetchPinnedAccountsSuggestionsRequest());
Expand Down
1 change: 1 addition & 0 deletions app/javascript/flavours/glitch/api_types/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ApiAccountJSON {
bot: boolean;
created_at: string;
discoverable: boolean;
indexable: boolean;
display_name: string;
emojis: ApiCustomEmojiJSON[];
fields: ApiAccountFieldJSON[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default class Retention extends PureComponent {
let content;

if (loading) {
content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />;
content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading' />;
} else {
content = (
<table className='retention__table'>
Expand Down
26 changes: 21 additions & 5 deletions app/javascript/flavours/glitch/components/loading_indicator.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { useIntl, defineMessages } from 'react-intl';

import { CircularProgress } from './circular_progress';

export const LoadingIndicator: React.FC = () => (
<div className='loading-indicator'>
<CircularProgress size={50} strokeWidth={6} />
</div>
);
const messages = defineMessages({
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
});

export const LoadingIndicator: React.FC = () => {
const intl = useIntl();

return (
<div
className='loading-indicator'
role='progressbar'
aria-busy
aria-live='polite'
aria-label={intl.formatMessage(messages.loading)}
>
<CircularProgress size={50} strokeWidth={6} />
</div>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import PropTypes from 'prop-types';

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

import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg';

import { Icon } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';

const Step = ({ label, description, icon, iconComponent, completed, onClick, href }) => {
export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
const content = (
<>
<div className='onboarding__steps__item__icon'>
Expand All @@ -29,6 +31,12 @@ const Step = ({ label, description, icon, iconComponent, completed, onClick, hre
{content}
</a>
);
} else if (to) {
return (
<Link to={to} className='onboarding__steps__item'>
{content}
</Link>
);
}

return (
Expand All @@ -45,7 +53,6 @@ Step.propTypes = {
iconComponent: PropTypes.func,
completed: PropTypes.bool,
href: PropTypes.string,
to: PropTypes.string,
onClick: PropTypes.func,
};

export default Step;
99 changes: 41 additions & 58 deletions app/javascript/flavours/glitch/features/onboarding/follows.jsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,62 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { useEffect } from 'react';

import { FormattedMessage } from 'react-intl';

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

import { useDispatch } from 'react-redux';


import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
import { markAsPartial } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column';
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
import { EmptyAccount } from 'flavours/glitch/components/empty_account';
import Account from 'flavours/glitch/containers/account_container';
import { useAppSelector } from 'flavours/glitch/store';

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

class Follows extends PureComponent {

static propTypes = {
onBack: PropTypes.func,
dispatch: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
};
export const Follows = () => {
const dispatch = useDispatch();
const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading']));
const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items']));

componentDidMount () {
const { dispatch } = this.props;
useEffect(() => {
dispatch(fetchSuggestions(true));
}

componentWillUnmount () {
const { dispatch } = this.props;
dispatch(markAsPartial('home'));
}

render () {
const { onBack, isLoading, suggestions } = this.props;
return () => {
dispatch(markAsPartial('home'));
};
}, [dispatch]);

let loadedContent;
let loadedContent;

if (isLoading) {
loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
} else if (suggestions.isEmpty()) {
loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
} else {
loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
}

return (
<Column>
<ColumnBackButton onClick={onBack} />

<div className='scrollable privacy-policy'>
<div className='column-title'>
<h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
<p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
</div>
if (isLoading) {
loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
} else if (suggestions.isEmpty()) {
loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
} else {
loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
}

<div className='follow-recommendations'>
{loadedContent}
</div>
return (
<>
<ColumnBackButton />

<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
<div className='scrollable privacy-policy'>
<div className='column-title'>
<h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
<p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
</div>

<div className='onboarding__footer'>
<button className='link-button' onClick={onBack}><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></button>
</div>
<div className='follow-recommendations'>
{loadedContent}
</div>
</Column>
);
}

}
<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>

export default connect(mapStateToProps)(Follows);
<div className='onboarding__footer'>
<Link className='link-button' to='/start'><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></Link>
</div>
</div>
</>
);
};
Loading
Loading