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 1/9] 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; From 580633c91549893059c4487168c4396031b43dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:26:27 +0200 Subject: [PATCH 2/9] AccountContainer: use withDate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/account.tsx | 4 ++++ .../features/admin/components/latest_accounts_panel.tsx | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 3288127a1..132b9dfa2 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -35,6 +35,7 @@ interface IAccount { showProfileHoverCard?: boolean, timestamp?: string | Date, timestampUrl?: string, + withDate?: boolean, withRelationship?: boolean, } @@ -51,6 +52,7 @@ const Account = ({ showProfileHoverCard = true, timestamp, timestampUrl, + withDate = false, withRelationship = true, }: IAccount) => { const overflowRef = React.useRef(null); @@ -122,6 +124,8 @@ const Account = ({ ); } + if (withDate) timestamp = account.created_at; + const LinkEl: any = showProfileHoverCard ? Link : 'div'; return ( diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx index ae32a40f5..7f53abd46 100644 --- a/app/soapbox/features/admin/components/latest_accounts_panel.tsx +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -24,10 +24,10 @@ const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const accountIds = useAppSelector>((state) => state.admin.get('latestUsers')); + const accountIds = useAppSelector>((state) => state.admin.get('latestUsers').take(limit)); const hasDates = useAppSelector((state) => accountIds.every(id => !!state.accounts.getIn([id, 'created_at']))); - const [total, setTotal] = useState(0); + const [total, setTotal] = useState(accountIds.size); useEffect(() => { dispatch(fetchUsers(['local', 'active'], 1, null, limit)) @@ -49,7 +49,7 @@ const LatestAccountsPanel: React.FC = ({ limit = 5 }) => { return ( {accountIds.take(limit).map((account) => ( - + ))} {!!expandCount && ( From bd1e6d364a189e7619fb3ceed64393f69cf94f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:36:07 +0200 Subject: [PATCH 3/9] AutosuggestInput dark styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/autosuggest_input.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/components/autosuggest_input.js b/app/soapbox/components/autosuggest_input.js index 1bb2e2edf..d13873c80 100644 --- a/app/soapbox/components/autosuggest_input.js +++ b/app/soapbox/components/autosuggest_input.js @@ -206,8 +206,8 @@ export default class AutosuggestInput extends ImmutablePureComponent { key={key} data-index={i} className={classNames({ - 'px-4 py-2.5 text-sm text-gray-700 cursor-pointer hover:bg-gray-100 group': true, - 'bg-gray-100 hover:bg-gray-100': i === selectedSuggestion, + 'px-4 py-2.5 text-sm text-gray-700 dark:text-gray-400 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 group': true, + 'bg-gray-100 dark:bg-slate-700 hover:bg-gray-100 dark:hover:bg-gray-700': i === selectedSuggestion, })} onMouseDown={this.onSuggestionClick} > @@ -238,7 +238,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { return menu.map((item, i) => (
Date: Thu, 14 Apr 2022 15:37:20 +0200 Subject: [PATCH 4/9] dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/styles/components/reply-mentions.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/components/reply-mentions.scss b/app/styles/components/reply-mentions.scss index f94ba5181..5312d0859 100644 --- a/app/styles/components/reply-mentions.scss +++ b/app/styles/components/reply-mentions.scss @@ -1,5 +1,5 @@ .reply-mentions { - @apply text-gray-500 mb-1 text-sm; + @apply text-gray-500 dark:text-gray-400 mb-1 text-sm; &__account { @apply text-primary-600 no-underline; From 23943ccdee732e10a72e955cba8c79f042d68227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 15:51:23 +0200 Subject: [PATCH 5/9] Remote interaction modal styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../ui/components/unauthorized_modal.js | 39 ++++++++----------- app/styles/components/modal.scss | 11 ++---- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/app/soapbox/features/ui/components/unauthorized_modal.js b/app/soapbox/features/ui/components/unauthorized_modal.js index 2e5a1a2f6..cf2c4d79e 100644 --- a/app/soapbox/features/ui/components/unauthorized_modal.js +++ b/app/soapbox/features/ui/components/unauthorized_modal.js @@ -3,16 +3,14 @@ import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { Link, withRouter } from 'react-router-dom'; +import { withRouter } from 'react-router-dom'; import { remoteInteraction } from 'soapbox/actions/interactions'; import snackbar from 'soapbox/actions/snackbar'; import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -import IconButton from 'soapbox/components/icon_button'; +import { Button, Modal, Stack, Text } from 'soapbox/components/ui'; import { getFeatures } from 'soapbox/utils/features'; -import { Modal, Stack, Text } from '../../../components/ui'; - const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, accountPlaceholder: { id: 'remote_interaction.account_placeholder', defaultMessage: 'Enter your username@domain you want to act from' }, @@ -133,11 +131,14 @@ class UnauthorizedModal extends ImmutablePureComponent { } return ( -
-
-

{header}

- -
+ } + secondaryAction={this.onRegister} + secondaryText={} + >
- +
- + - +
{!singleUserMode && ( - <> -

- - - - + + + )} - - -
-
+ ); } diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss index c04089318..e1a506187 100644 --- a/app/styles/components/modal.scss +++ b/app/styles/components/modal.scss @@ -817,9 +817,7 @@ &__content { display: flex; flex-direction: column; - // align-items: center; row-gap: 10px; - padding: 10px; .unauthorized-modal-content__button { margin: 0 auto; @@ -832,11 +830,8 @@ gap: 10px; width: 100%; - .button { - width: auto; - margin: 0; - text-transform: none; - overflow: unset; + button { + align-self: flex-end; } } @@ -848,9 +843,9 @@ &::before, &::after { + @apply border-b border-gray-300 dark:border-gray-600; content: ""; flex: 1; - border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); } } From 92a6058f26a082dd5cce12e8b309d815bd741914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 17:50:47 +0200 Subject: [PATCH 6/9] typescript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../{account_notes.js => account_notes.ts} | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) rename app/soapbox/reducers/{account_notes.js => account_notes.ts} (68%) diff --git a/app/soapbox/reducers/account_notes.js b/app/soapbox/reducers/account_notes.ts similarity index 68% rename from app/soapbox/reducers/account_notes.js rename to app/soapbox/reducers/account_notes.ts index 67cd83e1d..b7f6f2eab 100644 --- a/app/soapbox/reducers/account_notes.js +++ b/app/soapbox/reducers/account_notes.ts @@ -1,4 +1,5 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; +import { AnyAction } from 'redux'; import { ACCOUNT_NOTE_INIT_MODAL, @@ -8,15 +9,19 @@ import { ACCOUNT_NOTE_SUBMIT_SUCCESS, } from '../actions/account_notes'; -const initialState = ImmutableMap({ - edit: ImmutableMap({ - isSubmitting: false, - account: null, - comment: null, - }), +const EditRecord = ImmutableRecord({ + isSubmitting: false, + account: null, + comment: null, }); -export default function account_notes(state = initialState, action) { +const ReducerRecord = ImmutableRecord({ + edit: EditRecord(), +}); + +type State = ReturnType; + +export default function account_notes(state: State = ReducerRecord(), action: AnyAction) { switch (action.type) { case ACCOUNT_NOTE_INIT_MODAL: return state.withMutations((state) => { From f745c9fc9735e6d27655dfa3a31e85a8d4f601d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 18:10:46 +0200 Subject: [PATCH 7/9] typescript, FC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/ui/input/input.tsx | 2 +- .../components/latest_accounts_panel.tsx | 2 +- .../delete_account/{index.js => index.tsx} | 6 +- .../developers/developers_challenge.js | 92 ------------------- .../developers/developers_challenge.tsx | 80 ++++++++++++++++ ...developers_menu.js => developers_menu.tsx} | 2 +- app/soapbox/features/developers/index.js | 30 ------ app/soapbox/features/developers/index.tsx | 15 +++ app/soapbox/hooks/index.ts | 1 + 9 files changed, 102 insertions(+), 128 deletions(-) rename app/soapbox/features/delete_account/{index.js => index.tsx} (94%) delete mode 100644 app/soapbox/features/developers/developers_challenge.js create mode 100644 app/soapbox/features/developers/developers_challenge.tsx rename app/soapbox/features/developers/{developers_menu.js => developers_menu.tsx} (97%) delete mode 100644 app/soapbox/features/developers/index.js create mode 100644 app/soapbox/features/developers/index.tsx diff --git a/app/soapbox/components/ui/input/input.tsx b/app/soapbox/components/ui/input/input.tsx index 343a9d0dd..5314ee583 100644 --- a/app/soapbox/components/ui/input/input.tsx +++ b/app/soapbox/components/ui/input/input.tsx @@ -19,7 +19,7 @@ interface IInput extends Pick, 'onCh name?: string, placeholder?: string, value?: string, - onChange?: () => void, + onChange?: (event: React.ChangeEvent) => void, type: 'text' | 'email' | 'tel' | 'password' } diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.tsx b/app/soapbox/features/admin/components/latest_accounts_panel.tsx index 7f53abd46..b1e9a441b 100644 --- a/app/soapbox/features/admin/components/latest_accounts_panel.tsx +++ b/app/soapbox/features/admin/components/latest_accounts_panel.tsx @@ -9,7 +9,7 @@ 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'; +import { useAppDispatch } from 'soapbox/hooks'; const messages = defineMessages({ title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, diff --git a/app/soapbox/features/delete_account/index.js b/app/soapbox/features/delete_account/index.tsx similarity index 94% rename from app/soapbox/features/delete_account/index.js rename to app/soapbox/features/delete_account/index.tsx index f0d902e4a..733b9554c 100644 --- a/app/soapbox/features/delete_account/index.js +++ b/app/soapbox/features/delete_account/index.tsx @@ -1,11 +1,11 @@ import PropTypes from 'prop-types'; import * as React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import { deleteAccount } from 'soapbox/actions/security'; import snackbar from 'soapbox/actions/snackbar'; import { Button, Card, CardBody, CardHeader, CardTitle, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; const messages = defineMessages({ passwordFieldLabel: { id: 'security.fields.password.label', defaultMessage: 'Password' }, @@ -18,12 +18,12 @@ const messages = defineMessages({ const DeleteAccount = () => { const intl = useIntl(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [password, setPassword] = React.useState(''); const [isLoading, setLoading] = React.useState(false); - const handleInputChange = React.useCallback((event) => { + const handleInputChange = React.useCallback((event: React.ChangeEvent) => { event.persist(); setPassword(event.target.value); diff --git a/app/soapbox/features/developers/developers_challenge.js b/app/soapbox/features/developers/developers_challenge.js deleted file mode 100644 index b62191227..000000000 --- a/app/soapbox/features/developers/developers_challenge.js +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; -import { connect } from 'react-redux'; - -import { changeSettingImmediate } from 'soapbox/actions/settings'; -import snackbar from 'soapbox/actions/snackbar'; -import { Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui'; - - -import Column from '../ui/components/column'; - -const messages = defineMessages({ - heading: { id: 'column.developers', defaultMessage: 'Developers' }, - answerLabel: { id: 'developers.challenge.answer_label', defaultMessage: 'Answer' }, - answerPlaceholder: { id: 'developers.challenge.answer_placeholder', defaultMessage: 'Your answer' }, - success: { id: 'developers.challenge.success', defaultMessage: 'You are now a developer' }, - fail: { id: 'developers.challenge.fail', defaultMessage: 'Wrong answer' }, -}); - -export default @connect() -@injectIntl -class DevelopersChallenge extends React.Component { - - static propTypes = { - intl: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - } - - state = { - answer: '', - } - - handleChangeAnswer = e => { - this.setState({ answer: e.target.value }); - } - - handleSubmit = e => { - const { intl, dispatch } = this.props; - const { answer } = this.state; - - if (answer === 'boxsoap') { - dispatch(changeSettingImmediate(['isDeveloper'], true)); - dispatch(snackbar.success(intl.formatMessage(messages.success))); - } else { - dispatch(snackbar.error(intl.formatMessage(messages.fail))); - } - } - - render() { - const { intl } = this.props; - - const challenge = `function soapbox() { - return 'soap|box'.split('|').reverse().join(''); -}`; - - return ( - -
- - soapbox() }} - /> - - - {challenge} - - - - - - - - - -
-
- ); - } - -} diff --git a/app/soapbox/features/developers/developers_challenge.tsx b/app/soapbox/features/developers/developers_challenge.tsx new file mode 100644 index 000000000..d27b46429 --- /dev/null +++ b/app/soapbox/features/developers/developers_challenge.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { useState } from 'react'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import { useDispatch } from 'react-redux'; + +import { changeSettingImmediate } from 'soapbox/actions/settings'; +import snackbar from 'soapbox/actions/snackbar'; +import { Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui'; + + +import Column from '../ui/components/column'; + +const messages = defineMessages({ + heading: { id: 'column.developers', defaultMessage: 'Developers' }, + answerLabel: { id: 'developers.challenge.answer_label', defaultMessage: 'Answer' }, + answerPlaceholder: { id: 'developers.challenge.answer_placeholder', defaultMessage: 'Your answer' }, + success: { id: 'developers.challenge.success', defaultMessage: 'You are now a developer' }, + fail: { id: 'developers.challenge.fail', defaultMessage: 'Wrong answer' }, +}); + +const DevelopersChallenge = () => { + const dispatch = useDispatch(); + const intl = useIntl(); + + const [answer, setAnswer] = useState(''); + + const handleChangeAnswer = (e: React.ChangeEvent) => { + setAnswer(e.target.value); + }; + + const handleSubmit = () => { + if (answer === 'boxsoap') { + dispatch(changeSettingImmediate(['isDeveloper'], true)); + dispatch(snackbar.success(intl.formatMessage(messages.success))); + } else { + dispatch(snackbar.error(intl.formatMessage(messages.fail))); + } + }; + + const challenge = `function soapbox() { + return 'soap|box'.split('|').reverse().join(''); +}`; + + return ( + +
+ + soapbox() }} + /> + + + {challenge} + + + + + + + + + +
+
+ ); +}; + +export default DevelopersChallenge; diff --git a/app/soapbox/features/developers/developers_menu.js b/app/soapbox/features/developers/developers_menu.tsx similarity index 97% rename from app/soapbox/features/developers/developers_menu.js rename to app/soapbox/features/developers/developers_menu.tsx index 832c0bd60..6cabd36a0 100644 --- a/app/soapbox/features/developers/developers_menu.js +++ b/app/soapbox/features/developers/developers_menu.tsx @@ -20,7 +20,7 @@ const Developers = () => { const history = useHistory(); const intl = useIntl(); - const leaveDevelopers = (e) => { + const leaveDevelopers = (e: React.MouseEvent) => { e.preventDefault(); dispatch(changeSettingImmediate(['isDeveloper'], false)); diff --git a/app/soapbox/features/developers/index.js b/app/soapbox/features/developers/index.js deleted file mode 100644 index 3d58c9249..000000000 --- a/app/soapbox/features/developers/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { connect } from 'react-redux'; - -import { getSettings } from 'soapbox/actions/settings'; - -import DevelopersChallenge from './developers_challenge'; -import DevelopersMenu from './developers_menu'; - -const mapStateToProps = state => { - const settings = getSettings(state); - - return { - isDeveloper: settings.get('isDeveloper'), - }; -}; - -export default @connect(mapStateToProps) -class Developers extends React.Component { - - static propTypes = { - isDeveloper: PropTypes.bool.isRequired, - } - - render() { - const { isDeveloper } = this.props; - return isDeveloper ? : ; - } - -} diff --git a/app/soapbox/features/developers/index.tsx b/app/soapbox/features/developers/index.tsx new file mode 100644 index 000000000..a192f1683 --- /dev/null +++ b/app/soapbox/features/developers/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { getSettings } from 'soapbox/actions/settings'; +import { useAppSelector } from 'soapbox/hooks'; + +import DevelopersChallenge from './developers_challenge'; +import DevelopersMenu from './developers_menu'; + +const Developers: React.FC = () => { + const isDeveloper = useAppSelector((state) => getSettings(state).get('isDeveloper')); + + return isDeveloper ? : ; +}; + +export default Developers; diff --git a/app/soapbox/hooks/index.ts b/app/soapbox/hooks/index.ts index d3c5f4700..6ec919a94 100644 --- a/app/soapbox/hooks/index.ts +++ b/app/soapbox/hooks/index.ts @@ -1,3 +1,4 @@ +export { useAppDispatch } from './useAppDispatch'; export { useAppSelector } from './useAppSelector'; export { useFeatures } from './useFeatures'; export { useOnScreen } from './useOnScreen'; From 19aa4b254e4341d0c1cbce08c1db26ea0cdf0520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 22:14:54 +0200 Subject: [PATCH 8/9] Support 'status' notification type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/actions/notifications.js | 4 ++-- .../features/notifications/components/filter_bar.js | 7 +++++++ .../features/notifications/components/notification.js | 8 +++++++- app/soapbox/locales/pl.json | 2 ++ app/soapbox/reducers/notifications.js | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/soapbox/actions/notifications.js b/app/soapbox/actions/notifications.js index ce7e5eca0..66918c698 100644 --- a/app/soapbox/actions/notifications.js +++ b/app/soapbox/actions/notifications.js @@ -98,7 +98,7 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale, const isOnNotificationsPage = curPath === '/notifications'; - if (notification.type === 'mention') { + if (['mention', 'status'].includes(notification.type)) { const regex = regexFromFilters(filters); const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content); filtered = regex && regex.test(searchIndex); @@ -170,7 +170,7 @@ export function dequeueNotifications() { const excludeTypesFromSettings = getState => getSettings(getState()).getIn(['notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'move', 'pleroma:emoji_reaction']); + const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'status', 'poll', 'move', 'pleroma:emoji_reaction']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/soapbox/features/notifications/components/filter_bar.js b/app/soapbox/features/notifications/components/filter_bar.js index 05e182146..d443205a7 100644 --- a/app/soapbox/features/notifications/components/filter_bar.js +++ b/app/soapbox/features/notifications/components/filter_bar.js @@ -15,6 +15,7 @@ const messages = defineMessages({ follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, moves: { id: 'notifications.filter.moves', defaultMessage: 'Moves' }, emoji_reacts: { id: 'notifications.filter.emoji_reacts', defaultMessage: 'Emoji reacts' }, + statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' }, }); export default @injectIntl @@ -80,6 +81,12 @@ class NotificationFilterBar extends React.PureComponent { action: this.onClick('poll'), name: 'poll', }); + items.push({ + text: , + title: intl.formatMessage(messages.statuses), + action: this.onClick('status'), + name: 'status', + }); items.push({ text: , title: intl.formatMessage(messages.follows), diff --git a/app/soapbox/features/notifications/components/notification.js b/app/soapbox/features/notifications/components/notification.js index 77fa5762e..00911e68e 100644 --- a/app/soapbox/features/notifications/components/notification.js +++ b/app/soapbox/features/notifications/components/notification.js @@ -36,13 +36,14 @@ const buildLink = (account) => ( ); -export const NOTIFICATION_TYPES = ['follow', 'mention', 'favourite', 'reblog']; +export const NOTIFICATION_TYPES = ['follow', 'mention', 'favourite', 'reblog', 'status']; const icons = { follow: require('@tabler/icons/icons/user-plus.svg'), mention: require('@tabler/icons/icons/at.svg'), favourite: require('@tabler/icons/icons/heart.svg'), reblog: require('@tabler/icons/icons/repeat.svg'), + status: require('@tabler/icons/icons/home.svg'), }; const messages = { @@ -62,6 +63,10 @@ const messages = { id: 'notification.reblog', defaultMessage: '{name} re-TRUTH your TRUTH', }, + status: { + id: 'notification.status', + defaultMessage: '{name} just posted', + }, }; const buildMessage = (type, account) => { @@ -153,6 +158,7 @@ const Notification = (props) => { case 'favourite': case 'mention': case 'reblog': + case 'status': return ( { } // Mastodon can return status notifications with a null status - if (['mention', 'reblog', 'favourite', 'poll'].includes(notification.type) && !notification.status.id) { + if (['mention', 'reblog', 'favourite', 'poll', 'status'].includes(notification.type) && !notification.status.id) { return false; } From 2d0dbf6005141295c188cd468567db9a9ba530c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 14 Apr 2022 22:20:53 +0200 Subject: [PATCH 9/9] dark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/ui/components/actions_modal.js | 2 +- app/styles/components/modal.scss | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/ui/components/actions_modal.js b/app/soapbox/features/ui/components/actions_modal.js index bcac0c67c..aa0201ca9 100644 --- a/app/soapbox/features/ui/components/actions_modal.js +++ b/app/soapbox/features/ui/components/actions_modal.js @@ -46,7 +46,7 @@ const ActionsModal = ({ status, actions, onClick, onClose }) => { {({ top }) => (
{status && ( - +