From 39b819241f973e6f913667f3bda59083eaf4d142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:24:11 +0200 Subject: [PATCH] Dashboard styles, typescript, add useAppDispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../admin/components/latest_accounts_panel.js | 87 ------------------- .../components/latest_accounts_panel.tsx | 63 ++++++++++++++ app/soapbox/features/admin/index.js | 79 ++++++++--------- app/soapbox/features/admin/user_index.js | 1 + app/soapbox/hooks/useAppDispatch.ts | 5 ++ app/styles/components/admin.scss | 60 +------------ app/styles/components/wtf-panel.scss | 2 +- 7 files changed, 107 insertions(+), 190 deletions(-) delete mode 100644 app/soapbox/features/admin/components/latest_accounts_panel.js create mode 100644 app/soapbox/features/admin/components/latest_accounts_panel.tsx create mode 100644 app/soapbox/hooks/useAppDispatch.ts diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.js b/app/soapbox/features/admin/components/latest_accounts_panel.js deleted file mode 100644 index 6377b6810..000000000 --- a/app/soapbox/features/admin/components/latest_accounts_panel.js +++ /dev/null @@ -1,87 +0,0 @@ -import { is } from 'immutable'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; - -import { fetchUsers } from 'soapbox/actions/admin'; -import compareId from 'soapbox/compare_id'; -import AccountListPanel from 'soapbox/features/ui/components/account_list_panel'; - -const messages = defineMessages({ - title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, - expand: { id: 'admin.latest_accounts_panel.expand_message', defaultMessage: 'Click to see {count} more {count, plural, one {account} other {accounts}}' }, -}); - -const mapStateToProps = state => { - const accountIds = state.getIn(['admin', 'latestUsers']); - - // HACK: AdminAPI only recently started sorting new users at the top. - // Try a dirty check to see if the users are sorted properly, or don't show the panel. - // Probably works most of the time. - const sortedIds = accountIds.sort(compareId).reverse(); - const hasDates = accountIds.every(id => state.getIn(['accounts', id, 'created_at'])); - const isSorted = hasDates && is(accountIds, sortedIds); - - return { - isSorted, - accountIds, - }; -}; - -export default @connect(mapStateToProps) -@injectIntl -class LatestAccountsPanel extends ImmutablePureComponent { - - static propTypes = { - accountIds: ImmutablePropTypes.orderedSet.isRequired, - limit: PropTypes.number, - }; - - static defaultProps = { - limit: 5, - } - - state = { - total: 0, - } - - componentDidMount() { - const { dispatch, limit } = this.props; - - dispatch(fetchUsers(['local', 'active'], 1, null, limit)) - .then(({ count }) => { - this.setState({ total: count }); - }) - .catch(() => {}); - } - - render() { - const { intl, accountIds, limit, isSorted, ...props } = this.props; - const { total } = this.state; - - if (!isSorted || !accountIds || accountIds.isEmpty()) { - return null; - } - - const expandCount = total - accountIds.size; - - return ( - - ); - } - -} diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx new file mode 100644 index 000000000..ae32a40f5 --- /dev/null +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -0,0 +1,63 @@ +import { OrderedSet as ImmutableOrderedSet, is } from 'immutable'; +import React, { useState } from 'react'; +import { useEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { fetchUsers } from 'soapbox/actions/admin'; +import compareId from 'soapbox/compare_id'; +import { Text, Widget } from 'soapbox/components/ui'; +import AccountContainer from 'soapbox/containers/account_container'; +import { useAppSelector } from 'soapbox/hooks'; +import { useAppDispatch } from 'soapbox/hooks/useAppDispatch'; + +const messages = defineMessages({ + title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, + expand: { id: 'admin.latest_accounts_panel.expand_message', defaultMessage: 'Click to see {count} more {count, plural, one {account} other {accounts}}' }, +}); + +interface ILatestAccountsPanel { + limit?: number, +} + +const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const accountIds = useAppSelector>((state) => state.admin.get('latestUsers')); + const hasDates = useAppSelector((state) => accountIds.every(id => !!state.accounts.getIn([id, 'created_at']))); + + const [total, setTotal] = useState(0); + + useEffect(() => { + dispatch(fetchUsers(['local', 'active'], 1, null, limit)) + .then((value) => { + setTotal((value as { count: number }).count); + }) + .catch(() => {}); + }, []); + + const sortedIds = accountIds.sort(compareId).reverse(); + const isSorted = hasDates && is(accountIds, sortedIds); + + if (!isSorted || !accountIds || accountIds.isEmpty()) { + return null; + } + + const expandCount = total - accountIds.size; + + return ( + + {accountIds.take(limit).map((account) => ( + + ))} + {!!expandCount && ( + + {intl.formatMessage(messages.expand, { count: expandCount })} + + )} + + ); +}; + +export default LatestAccountsPanel; diff --git a/app/soapbox/features/admin/index.js b/app/soapbox/features/admin/index.js index 09f626f0e..93a38c53f 100644 --- a/app/soapbox/features/admin/index.js +++ b/app/soapbox/features/admin/index.js @@ -7,6 +7,7 @@ import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email_list'; +import { Text } from 'soapbox/components/ui'; import sourceCode from 'soapbox/utils/code'; import { parseVersion } from 'soapbox/utils/features'; import { getFeatures } from 'soapbox/utils/features'; @@ -86,56 +87,46 @@ class Dashboard extends ImmutablePureComponent {
{mau &&
-
-
- -
-
- -
-
+ + + + + +
} -
- -
- -
-
- -
- -
+ + + + + + + + {isNumber(retention) && (
-
-
- {retention}% -
-
- -
-
+ + {retention}% + + + +
)} + + + + + + + +
- -
- -
-
- -
- -
-
-
-
- -
-
- -
-
+ + + + + +
{account.admin && } diff --git a/app/soapbox/features/admin/user_index.js b/app/soapbox/features/admin/user_index.js index d93f99d28..dc7691f69 100644 --- a/app/soapbox/features/admin/user_index.js +++ b/app/soapbox/features/admin/user_index.js @@ -116,6 +116,7 @@ class UserIndex extends ImmutablePureComponent { showLoading={showLoading} onLoadMore={this.handleLoadMore} emptyMessage={intl.formatMessage(messages.empty)} + className='mt-4 space-y-4' > {accountIds.map(id => , diff --git a/app/soapbox/hooks/useAppDispatch.ts b/app/soapbox/hooks/useAppDispatch.ts new file mode 100644 index 000000000..11e7226d5 --- /dev/null +++ b/app/soapbox/hooks/useAppDispatch.ts @@ -0,0 +1,5 @@ +import { useDispatch } from 'react-redux'; + +import { AppDispatch } from 'soapbox/store'; + +export const useAppDispatch = () => useDispatch(); \ No newline at end of file diff --git a/app/styles/components/admin.scss b/app/styles/components/admin.scss index 88a34b05c..56e7e9022 100644 --- a/app/styles/components/admin.scss +++ b/app/styles/components/admin.scss @@ -1,65 +1,9 @@ .dashcounters { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); - margin: 0 -5px 0; - padding: 20px; + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 mb-4; } .dashcounter { - box-sizing: border-box; - flex: 0 0 33.333%; - padding: 0 5px; - margin-bottom: 10px; - - > a, - > div { - box-sizing: border-box; - text-decoration: none; - color: inherit; - display: block; - padding: 20px; - background: var(--accent-color--faint); - border-radius: 4px; - transition: 0.2s; - height: 100%; - } - - > a:hover { - background: var(--accent-color--med); - transform: translateY(-2px); - } - - &__num, - &__icon, - &__text { - text-align: center; - font-weight: 500; - font-size: 24px; - line-height: 30px; - color: var(--primary-text-color); - margin-bottom: 10px; - } - - &__icon { - display: flex; - justify-content: center; - - .svg-icon { - width: 48px; - height: 48px; - - svg { - stroke-width: 1px; - } - } - } - - &__label { - font-size: 14px; - color: hsla(var(--primary-text-color_hsl), 0.6); - text-align: center; - font-weight: 500; - } + @apply bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center space-y-2 hover:-translate-y-1 transition-transform cursor-pointer; } .dashwidgets { diff --git a/app/styles/components/wtf-panel.scss b/app/styles/components/wtf-panel.scss index 120abe338..113e33e1f 100644 --- a/app/styles/components/wtf-panel.scss +++ b/app/styles/components/wtf-panel.scss @@ -139,13 +139,13 @@ } &__expand-btn { + @apply border-gray-300 dark:border-gray-600; display: block; width: 100%; height: 100%; max-height: 46px; position: relative; border-top: 1px solid; - border-color: var(--brand-color--faint); transition: max-height 150ms ease; overflow: hidden; opacity: 1;