Merge branch 'next_' into 'next'

next

See merge request soapbox-pub/soapbox-fe!1216
This commit is contained in:
Alex Gleason 2022-04-15 14:40:19 +00:00
commit 4bc6059d2d
27 changed files with 181 additions and 72 deletions

Binary file not shown.

View file

@ -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<HTMLDivElement>(null);
@ -122,6 +124,8 @@ const Account = ({
);
}
if (withDate) timestamp = account.created_at;
const LinkEl: any = showProfileHoverCard ? Link : 'div';
return (

View file

@ -19,7 +19,7 @@ interface IInput extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'onCh
name?: string,
placeholder?: string,
value?: string,
onChange?: () => void,
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void,
type: 'text' | 'email' | 'tel' | 'password'
}

View file

@ -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';
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<ILatestAccountsPanel> = ({ limit = 5 }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector<ImmutableOrderedSet<string>>((state) => state.admin.get('latestUsers').take(limit));
const hasDates = useAppSelector((state) => accountIds.every(id => !!state.accounts.getIn([id, 'created_at'])));
const [total, setTotal] = useState(accountIds.size);
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 (
<Widget title={intl.formatMessage(messages.title)}>
{accountIds.take(limit).map((account) => (
<AccountContainer key={account} id={account} withRelationship={false} withDate />
))}
{!!expandCount && (
<Link className='wtf-panel__expand-btn' to='/admin/users'>
<Text>{intl.formatMessage(messages.expand, { count: expandCount })}</Text>
</Link>
)}
</Widget>
);
};
export default LatestAccountsPanel;

Binary file not shown.

View file

@ -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<HTMLInputElement>) => {
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 (
<Column label={intl.formatMessage(messages.heading)}>
<Form onSubmit={handleSubmit}>
<Text>
<FormattedMessage
id='developers.challenge.message'
defaultMessage='What is the result of calling {function}?'
values={{ function: <span className='font-mono'>soapbox()</span> }}
/>
</Text>
<Text tag='pre' family='mono'>
{challenge}
</Text>
<FormGroup
labelText={intl.formatMessage(messages.answerLabel)}
>
<Input
name='answer'
placeholder={intl.formatMessage(messages.answerPlaceholder)}
onChange={handleChangeAnswer}
value={answer}
type='text'
/>
</FormGroup>
<FormActions>
<Button theme='primary' type='submit'>
<FormattedMessage id='developers.challenge.submit' defaultMessage='Become a developer' />
</Button>
</FormActions>
</Form>
</Column>
);
};
export default DevelopersChallenge;

View file

@ -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 ? <DevelopersMenu /> : <DevelopersChallenge />;
};
export default Developers;

View file

@ -1,3 +1,4 @@
export { useAppDispatch } from './useAppDispatch';
export { useAppSelector } from './useAppSelector';
export { useFeatures } from './useFeatures';
export { useOnScreen } from './useOnScreen';

View file

@ -0,0 +1,5 @@
import { useDispatch } from 'react-redux';
import { AppDispatch } from 'soapbox/store';
export const useAppDispatch = () => useDispatch<AppDispatch>();

View file

@ -694,6 +694,7 @@
"notification.pleroma:emoji_reaction": "{name} zareagował(a) na Twój wpis",
"notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się",
"notification.reblog": "{name} podbił(a) Twój wpis",
"notification.status": "{name} właśnie opublikował(a) wpis",
"notifications.clear": "Wyczyść powiadomienia",
"notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
"notifications.clear_heading": "Wyczyść powiadomienia",
@ -725,6 +726,7 @@
"notifications.filter.mentions": "Wspomienia",
"notifications.filter.moves": "Przenoszone konta",
"notifications.filter.polls": "Wyniki głosowania",
"notifications.filter.statuses": "Nowe wpisy osób, które subskrybujesz",
"notifications.group": "{count, number} {count, plural, one {powiadomienie} few {powiadomienia} many {powiadomień} more {powiadomień}}",
"notifications.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowe powiadomienie} few {nowe powiadomienia} many {nowych powiadomień} other {nowe powiadomienia}}",
"password_reset.confirmation": "Sprawdź swoją pocztę e-mail, aby potwierdzić.",

View file

@ -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 {

View file

@ -341,7 +341,7 @@
.actions-modal {
.dropdown-menu__separator {
@apply block m-2 h-[1px] bg-gray-200;
@apply block m-2 h-[1px] bg-gray-200 dark:bg-gray-600;
}
}
@ -521,7 +521,7 @@
}
.actions-modal {
@apply w-full max-h-full max-w-lg mt-auto mb-2 bg-white;
@apply w-full max-h-full max-w-lg mt-auto mb-2 bg-white dark:bg-slate-800;
.status {
overflow-y: auto;
@ -540,7 +540,7 @@
li:not(:empty) {
a,
button {
@apply flex items-center px-4 py-3 text-gray-600 no-underline hover:bg-gray-100 hover:text-gray-800;
@apply flex items-center px-4 py-3 text-gray-600 dark:text-gray-300 no-underline hover:bg-gray-100 dark:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200;
&.destructive {
@apply text-danger-600;
@ -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);
}
}

View file

@ -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;

View file

@ -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;