+++ /dev/null
-import api from 'flavours/glitch/util/api';
-
-export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST';
-export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS';
-export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL';
-
-export const fetchRules = () => (dispatch, getState) => {
- dispatch(fetchRulesRequest());
-
- api(getState)
- .get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules)))
- .catch(err => dispatch(fetchRulesFail(err)));
-};
-
-const fetchRulesRequest = () => ({
- type: RULES_FETCH_REQUEST,
-});
-
-const fetchRulesSuccess = rules => ({
- type: RULES_FETCH_SUCCESS,
- rules,
-});
-
-const fetchRulesFail = error => ({
- type: RULES_FETCH_FAIL,
- error,
-});
--- /dev/null
+import api from 'flavours/glitch/util/api';
+import { importFetchedAccount } from './importer';
+
+export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
+export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
+export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
+
+export const fetchServer = () => (dispatch, getState) => {
+ dispatch(fetchServerRequest());
+
+ api(getState)
+ .get('/api/v2/instance').then(({ data }) => {
+ if (data.contact.account) dispatch(importFetchedAccount(data.contact.account));
+ dispatch(fetchServerSuccess(data));
+ }).catch(err => dispatch(fetchServerFail(err)));
+};
+
+const fetchServerRequest = () => ({
+ type: SERVER_FETCH_REQUEST,
+});
+
+const fetchServerSuccess = server => ({
+ type: SERVER_FETCH_SUCCESS,
+ server,
+});
+
+const fetchServerFail = error => ({
+ type: SERVER_FETCH_FAIL,
+ error,
+});
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from 'flavours/glitch/util/initial_state';
import RelativeTimestamp from './relative_timestamp';
+import Skeleton from 'flavours/glitch/components/skeleton';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
class Account extends ImmutablePureComponent {
static propTypes = {
- account: ImmutablePropTypes.map.isRequired,
+ account: ImmutablePropTypes.map,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
} = this.props;
if (!account) {
- return <div />;
+ return (
+ <div className='account'>
+ <div className='account__wrapper'>
+ <div className='account__display-name'>
+ <div className='account__avatar-wrapper'><Skeleton width={36} height={36} /></div>
+ <DisplayName />
+ </div>
+ </div>
+ </div>
+ );
}
if (hidden) {
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
+import Skeleton from 'flavours/glitch/components/skeleton';
export default class DisplayName extends React.PureComponent {
const computedClass = classNames('display-name', { inline }, className);
- if (!account) return null;
-
let displayName, suffix;
+ let acct;
- let acct = account.get('acct');
+ if (account) {
+ acct = account.get('acct');
- if (acct.indexOf('@') === -1 && localDomain) {
- acct = `${acct}@${localDomain}`;
+ if (acct.indexOf('@') === -1 && localDomain) {
+ acct = `${acct}@${localDomain}`;
+ }
}
if (others && others.size > 0) {
<span className='display-name__account'>@{acct}</span>
</a>
);
- } else {
+ } else if (account) {
displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
suffix = <span className='display-name__account'>@{acct}</span>;
+ } else {
+ displayName = <bdi><strong className='display-name__html'><Skeleton width='10ch' /></strong></bdi>;
+ suffix = <span className='display-name__account'><Skeleton width='7ch' /></span>;
}
return (
--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+import { domain } from 'flavours/glitch/util/initial_state';
+import { fetchServer } from 'flavours/glitch/actions/server';
+import { connect } from 'react-redux';
+import Account from 'flavours/glitch/containers/account_container';
+import ShortNumber from 'flavours/glitch/components/short_number';
+import Skeleton from 'flavours/glitch/components/skeleton';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+
+const messages = defineMessages({
+ aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
+});
+
+const mapStateToProps = state => ({
+ server: state.get('server'),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ServerBanner extends React.PureComponent {
+
+ static propTypes = {
+ server: PropTypes.object,
+ dispatch: PropTypes.func,
+ intl: PropTypes.object,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchServer());
+ }
+
+ render () {
+ const { server, intl } = this.props;
+ const isLoading = server.get('isLoading');
+
+ return (
+ <div className='server-banner'>
+ <div className='server-banner__introduction'>
+ <FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
+ </div>
+
+ <img src={server.get('thumbnail')} alt={server.get('title')} className='server-banner__hero' />
+
+ <div className='server-banner__description'>
+ {isLoading ? (
+ <>
+ <Skeleton width='100%' />
+ <br />
+ <Skeleton width='100%' />
+ <br />
+ <Skeleton width='70%' />
+ </>
+ ) : server.get('description')}
+ </div>
+
+ <div className='server-banner__meta'>
+ <div className='server-banner__meta__column'>
+ <h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
+
+ <Account id={server.getIn(['contact', 'account', 'id'])} />
+ </div>
+
+ <div className='server-banner__meta__column'>
+ <h4><FormattedMessage id='server_banner.server_stats' defaultMessage='Server stats:' /></h4>
+
+ {isLoading ? (
+ <>
+ <strong className='server-banner__number'><Skeleton width='10ch' /></strong>
+ <br />
+ <span className='server-banner__number-label'><Skeleton width='5ch' /></span>
+ </>
+ ) : (
+ <>
+ <strong className='server-banner__number'><ShortNumber value={server.getIn(['usage', 'users', 'active_month'])} /></strong>
+ <br />
+ <span className='server-banner__number-label' title={intl.formatMessage(messages.aboutActiveUsers)}><FormattedMessage id='server_banner.active_users' defaultMessage='active users' /></span>
+ </>
+ )}
+ </div>
+ </div>
+
+ <hr className='spacer' />
+
+ <a className='button button--block button-secondary' href='/about/more' target='_blank'><FormattedMessage id='server_banner.learn_more' defaultMessage='Learn more' /></a>
+ </div>
+ );
+ }
+
+}
import Option from './components/option';
const mapStateToProps = state => ({
- rules: state.get('rules'),
+ rules: state.getIn(['server', 'rules']),
});
export default @connect(mapStateToProps)
import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container';
import LinkFooter from './link_footer';
+import ServerBanner from 'flavours/glitch/components/server_banner';
export default
class ComposePanel extends React.PureComponent {
{!signedIn && (
<React.Fragment>
+ <ServerBanner />
<div className='flex-spacer' />
</React.Fragment>
)}
import { connect } from 'react-redux';
import { submitReport } from 'flavours/glitch/actions/reports';
import { expandAccountTimeline } from 'flavours/glitch/actions/timelines';
-import { fetchRules } from 'flavours/glitch/actions/rules';
+import { fetchServer } from 'flavours/glitch/actions/server';
import { fetchRelationships } from 'flavours/glitch/actions/accounts';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
dispatch(fetchRelationships([accountId]));
dispatch(expandAccountTimeline(accountId, { withReplies: true }));
- dispatch(fetchRules());
+ dispatch(fetchServer());
}
render () {
import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
-import { fetchRules } from 'flavours/glitch/actions/rules';
+import { fetchServer } from 'flavours/glitch/actions/server';
import { clearHeight } from 'flavours/glitch/actions/height_cache';
import { changeLayout } from 'flavours/glitch/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
- setTimeout(() => this.props.dispatch(fetchRules()), 3000);
+ setTimeout(() => this.props.dispatch(fetchServer()), 3000);
}
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
import status_lists from './status_lists';
import mutes from './mutes';
import blocks from './blocks';
-import rules from './rules';
+import server from './server';
import boosts from './boosts';
import contexts from './contexts';
import compose from './compose';
push_notifications,
mutes,
blocks,
- rules,
+ server,
boosts,
contexts,
compose,
+++ /dev/null
-import { RULES_FETCH_SUCCESS } from 'flavours/glitch/actions/rules';
-import { List as ImmutableList, fromJS } from 'immutable';
-
-const initialState = ImmutableList();
-
-export default function rules(state = initialState, action) {
- switch (action.type) {
- case RULES_FETCH_SUCCESS:
- return fromJS(action.rules);
- default:
- return state;
- }
-}
--- /dev/null
+import { SERVER_FETCH_REQUEST, SERVER_FETCH_SUCCESS, SERVER_FETCH_FAIL } from 'flavours/glitch/actions/server';
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+ isLoading: true,
+});
+
+export default function server(state = initialState, action) {
+ switch (action.type) {
+ case SERVER_FETCH_REQUEST:
+ return state.set('isLoading', true);
+ case SERVER_FETCH_SUCCESS:
+ return fromJS(action.server).set('isLoading', false);
+ case SERVER_FETCH_FAIL:
+ return state.set('isLoading', false);
+ default:
+ return state;
+ }
+}
margin-bottom: 10px;
}
}
+
+.server-banner {
+ padding: 20px 0;
+
+ &__introduction {
+ color: $darker-text-color;
+ margin-bottom: 20px;
+
+ strong {
+ font-weight: 600;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+
+ &:hover,
+ &:active,
+ &:focus {
+ text-decoration: none;
+ }
+ }
+ }
+
+ &__hero {
+ display: block;
+ border-radius: 4px;
+ width: 100%;
+ height: auto;
+ margin-bottom: 20px;
+ aspect-ratio: 1.9;
+ border: 0;
+ background: $ui-base-color;
+ object-fit: cover;
+ }
+
+ &__description {
+ margin-bottom: 20px;
+ }
+
+ &__meta {
+ display: flex;
+ gap: 10px;
+ max-width: 100%;
+
+ &__column {
+ flex: 0 0 auto;
+ width: calc(50% - 5px);
+ overflow: hidden;
+ }
+ }
+
+ &__number {
+ font-weight: 600;
+ color: $primary-text-color;
+ }
+
+ &__number-label {
+ color: $darker-text-color;
+ font-weight: 500;
+ }
+
+ h4 {
+ text-transform: uppercase;
+ color: $darker-text-color;
+ margin-bottom: 10px;
+ font-weight: 600;
+ }
+
+ .account {
+ padding: 0;
+ border: 0;
+ }
+
+ .account__avatar-wrapper {
+ margin-left: 0;
+ }
+
+ .spacer {
+ margin: 10px 0;
+ }
+}
export const title = getMeta('title');
export const disableSwiping = getMeta('disable_swiping');
export const languages = initialState && initialState.languages;
-export const server = initialState && initialState.server;
export default initialState;