about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch')
-rw-r--r--app/javascript/flavours/glitch/actions/rules.js27
-rw-r--r--app/javascript/flavours/glitch/actions/server.js30
-rw-r--r--app/javascript/flavours/glitch/components/account.js14
-rw-r--r--app/javascript/flavours/glitch/components/display_name.js17
-rw-r--r--app/javascript/flavours/glitch/components/server_banner.js91
-rw-r--r--app/javascript/flavours/glitch/features/report/rules.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/compose_panel.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js4
-rw-r--r--app/javascript/flavours/glitch/reducers/index.js4
-rw-r--r--app/javascript/flavours/glitch/reducers/rules.js13
-rw-r--r--app/javascript/flavours/glitch/reducers/server.js19
-rw-r--r--app/javascript/flavours/glitch/styles/components/signed_out.scss82
-rw-r--r--app/javascript/flavours/glitch/util/initial_state.js1
14 files changed, 254 insertions, 56 deletions
diff --git a/app/javascript/flavours/glitch/actions/rules.js b/app/javascript/flavours/glitch/actions/rules.js
deleted file mode 100644
index b95045e81..000000000
--- a/app/javascript/flavours/glitch/actions/rules.js
+++ /dev/null
@@ -1,27 +0,0 @@
-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,
-});
diff --git a/app/javascript/flavours/glitch/actions/server.js b/app/javascript/flavours/glitch/actions/server.js
new file mode 100644
index 000000000..215dfeffa
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/server.js
@@ -0,0 +1,30 @@
+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,
+});
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js
index 489f60736..24d3f65ef 100644
--- a/app/javascript/flavours/glitch/components/account.js
+++ b/app/javascript/flavours/glitch/components/account.js
@@ -9,6 +9,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 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' },
@@ -26,7 +27,7 @@ export default @injectIntl
 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,
@@ -77,7 +78,16 @@ class Account extends ImmutablePureComponent {
     } = 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) {
diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js
index 9c7da744e..7cb0c9133 100644
--- a/app/javascript/flavours/glitch/components/display_name.js
+++ b/app/javascript/flavours/glitch/components/display_name.js
@@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 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 {
 
@@ -46,14 +47,15 @@ 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) {
@@ -80,9 +82,12 @@ export default class DisplayName extends React.PureComponent {
           <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 (
diff --git a/app/javascript/flavours/glitch/components/server_banner.js b/app/javascript/flavours/glitch/components/server_banner.js
new file mode 100644
index 000000000..e29876d4b
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/server_banner.js
@@ -0,0 +1,91 @@
+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>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/report/rules.js b/app/javascript/flavours/glitch/features/report/rules.js
index 4772e04a2..599c04dbd 100644
--- a/app/javascript/flavours/glitch/features/report/rules.js
+++ b/app/javascript/flavours/glitch/features/report/rules.js
@@ -7,7 +7,7 @@ import Button from 'flavours/glitch/components/button';
 import Option from './components/option';
 
 const mapStateToProps = state => ({
-  rules: state.get('rules'),
+  rules: state.getIn(['server', 'rules']),
 });
 
 export default @connect(mapStateToProps)
diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
index 298c15a8a..6e1c51d74 100644
--- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
@@ -4,6 +4,7 @@ import SearchContainer from 'flavours/glitch/features/compose/containers/search_
 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 {
@@ -21,6 +22,7 @@ class ComposePanel extends React.PureComponent {
 
         {!signedIn && (
           <React.Fragment>
+            <ServerBanner />
             <div className='flex-spacer' />
           </React.Fragment>
         )}
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
index dcbe94929..7b6a4a784 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
@@ -2,7 +2,7 @@ import React from 'react';
 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';
@@ -119,7 +119,7 @@ class ReportModal extends ImmutablePureComponent {
 
     dispatch(fetchRelationships([accountId]));
     dispatch(expandAccountTimeline(accountId, { withReplies: true }));
-    dispatch(fetchRules());
+    dispatch(fetchServer());
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 6c60a86c4..735623e3d 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -10,7 +10,7 @@ import { debounce } from 'lodash';
 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';
@@ -408,7 +408,7 @@ class UI extends React.Component {
       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) => {
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index 991b4aa79..09c08a362 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -17,7 +17,7 @@ import push_notifications from './push_notifications';
 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';
@@ -64,7 +64,7 @@ const reducers = {
   push_notifications,
   mutes,
   blocks,
-  rules,
+  server,
   boosts,
   contexts,
   compose,
diff --git a/app/javascript/flavours/glitch/reducers/rules.js b/app/javascript/flavours/glitch/reducers/rules.js
deleted file mode 100644
index 6cc2230bc..000000000
--- a/app/javascript/flavours/glitch/reducers/rules.js
+++ /dev/null
@@ -1,13 +0,0 @@
-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;
-  }
-}
diff --git a/app/javascript/flavours/glitch/reducers/server.js b/app/javascript/flavours/glitch/reducers/server.js
new file mode 100644
index 000000000..977574148
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/server.js
@@ -0,0 +1,19 @@
+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;
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/signed_out.scss b/app/javascript/flavours/glitch/styles/components/signed_out.scss
index 74eccf497..a318bf66b 100644
--- a/app/javascript/flavours/glitch/styles/components/signed_out.scss
+++ b/app/javascript/flavours/glitch/styles/components/signed_out.scss
@@ -10,3 +10,85 @@
     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;
+  }
+}
diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js
index b02a52ea5..99ee6bc69 100644
--- a/app/javascript/flavours/glitch/util/initial_state.js
+++ b/app/javascript/flavours/glitch/util/initial_state.js
@@ -40,6 +40,5 @@ export const showTrends = getMeta('trends');
 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;