Prefer accessible links
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
eb05a67671
commit
14e2e07305
9 changed files with 128 additions and 178 deletions
|
@ -1,5 +1,6 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { SelectDropdown } from '../features/forms';
|
import { SelectDropdown } from '../features/forms';
|
||||||
|
@ -17,13 +18,14 @@ const List: React.FC<IList> = ({ children }) => (
|
||||||
interface IListItem {
|
interface IListItem {
|
||||||
label: React.ReactNode
|
label: React.ReactNode
|
||||||
hint?: React.ReactNode
|
hint?: React.ReactNode
|
||||||
|
to?: string
|
||||||
onClick?(): void
|
onClick?(): void
|
||||||
onSelect?(): void
|
onSelect?(): void
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelect, isSelected }) => {
|
const ListItem: React.FC<IListItem> = ({ label, hint, children, to, onClick, onSelect, isSelected }) => {
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
const domId = `list-group-${id}`;
|
const domId = `list-group-${id}`;
|
||||||
|
|
||||||
|
@ -33,9 +35,9 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Comp = onClick ? 'a' : 'div';
|
const Comp = to ? Link : (onClick ? 'a' : 'div');
|
||||||
const LabelComp = onClick || onSelect ? 'span' : 'label';
|
const LabelComp = to || onClick || onSelect ? 'span' : 'label';
|
||||||
const linkProps = onClick || onSelect ? { onClick: onClick || onSelect, onKeyDown, tabIndex: 0, role: 'link' } : {};
|
const linkProps = to ? { to } : (onClick || onSelect ? { onClick: onClick || onSelect, onKeyDown, tabIndex: 0, role: 'link' } : {});
|
||||||
|
|
||||||
const renderChildren = React.useCallback(() => {
|
const renderChildren = React.useCallback(() => {
|
||||||
return React.Children.map(children, (child) => {
|
return React.Children.map(children, (child) => {
|
||||||
|
@ -58,7 +60,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={clsx('flex items-center justify-between overflow-hidden bg-gradient-to-r from-gradient-start/20 to-gradient-end/20 px-4 py-2 first:rounded-t-lg last:rounded-b-lg dark:from-gradient-start/10 dark:to-gradient-end/10', {
|
className={clsx('flex items-center justify-between overflow-hidden bg-gradient-to-r from-gradient-start/20 to-gradient-end/20 px-4 py-2 first:rounded-t-lg last:rounded-b-lg dark:from-gradient-start/10 dark:to-gradient-end/10', {
|
||||||
'cursor-pointer hover:from-gradient-start/30 hover:to-gradient-end/30 dark:hover:from-gradient-start/5 dark:hover:to-gradient-end/5': typeof onClick !== 'undefined' || typeof onSelect !== 'undefined',
|
'cursor-pointer hover:from-gradient-start/30 hover:to-gradient-end/30 dark:hover:from-gradient-start/5 dark:hover:to-gradient-end/5': typeof to !== 'undefined' || typeof onClick !== 'undefined' || typeof onSelect !== 'undefined',
|
||||||
})}
|
})}
|
||||||
{...linkProps}
|
{...linkProps}
|
||||||
>
|
>
|
||||||
|
@ -70,7 +72,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{onClick ? (
|
{(to || onClick) ? (
|
||||||
<HStack space={1} alignItems='center' className='overflow-hidden text-gray-700 dark:text-gray-600'>
|
<HStack space={1} alignItems='center' className='overflow-hidden text-gray-700 dark:text-gray-600'>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{typeof onClick === 'undefined' && typeof onSelect === 'undefined' ? renderChildren() : null}
|
{typeof to === 'undefined' && typeof onClick === 'undefined' && typeof onSelect === 'undefined' ? renderChildren() : null}
|
||||||
</Comp>
|
</Comp>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email-list';
|
import { getSubscribersCsv, getUnsubscribersCsv, getCombinedCsv } from 'soapbox/actions/email-list';
|
||||||
import List, { ListItem } from 'soapbox/components/list';
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
@ -15,7 +14,6 @@ import RegistrationModePicker from '../components/registration-mode-picker';
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const history = useHistory();
|
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
|
@ -41,10 +39,6 @@ const Dashboard: React.FC = () => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToSoapboxConfig = () => history.push('/soapbox/config');
|
|
||||||
const navigateToModerationLog = () => history.push('/soapbox/admin/log');
|
|
||||||
const navigateToAnnouncements = () => history.push('/soapbox/admin/announcements');
|
|
||||||
|
|
||||||
const v = parseVersion(instance.version);
|
const v = parseVersion(instance.version);
|
||||||
|
|
||||||
const userCount = instance.stats.get('user_count');
|
const userCount = instance.stats.get('user_count');
|
||||||
|
@ -87,19 +81,19 @@ const Dashboard: React.FC = () => {
|
||||||
<List>
|
<List>
|
||||||
{account.admin && (
|
{account.admin && (
|
||||||
<ListItem
|
<ListItem
|
||||||
onClick={navigateToSoapboxConfig}
|
to='/soapbox/config'
|
||||||
label={<FormattedMessage id='navigation_bar.soapbox_config' defaultMessage='Soapbox config' />}
|
label={<FormattedMessage id='navigation_bar.soapbox_config' defaultMessage='Soapbox config' />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
onClick={navigateToModerationLog}
|
to='/soapbox/admin/log'
|
||||||
label={<FormattedMessage id='column.admin.moderation_log' defaultMessage='Moderation Log' />}
|
label={<FormattedMessage id='column.admin.moderation_log' defaultMessage='Moderation Log' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{features.announcements && (
|
{features.announcements && (
|
||||||
<ListItem
|
<ListItem
|
||||||
onClick={navigateToAnnouncements}
|
to='/soapbox/admin/announcements'
|
||||||
label={<FormattedMessage id='column.admin.announcements' defaultMessage='Announcements' />}
|
label={<FormattedMessage id='column.admin.announcements' defaultMessage='Announcements' />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { changeEmail } from 'soapbox/actions/security';
|
import { changeEmail } from 'soapbox/actions/security';
|
||||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
import { Button, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
@ -48,47 +48,33 @@ const EditEmail = () => {
|
||||||
}, [email, password, dispatch, intl]);
|
}, [email, password, dispatch, intl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column label={intl.formatMessage(messages.header)} backHref='/settings'>
|
||||||
label={intl.formatMessage(messages.header)}
|
<Form onSubmit={handleSubmit}>
|
||||||
transparent
|
<FormGroup labelText={intl.formatMessage(messages.emailFieldLabel)}>
|
||||||
withHeader={false}
|
<Input
|
||||||
>
|
type='text'
|
||||||
<Card variant='rounded'>
|
placeholder={intl.formatMessage(messages.emailFieldPlaceholder)}
|
||||||
<CardHeader backHref='/settings'>
|
name='email'
|
||||||
<CardTitle
|
autoComplete='off'
|
||||||
title={intl.formatMessage(messages.header)}
|
onChange={handleInputChange}
|
||||||
|
value={email}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</FormGroup>
|
||||||
|
|
||||||
<CardBody>
|
<FormGroup labelText={intl.formatMessage(messages.passwordFieldLabel)}>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Input
|
||||||
<FormGroup labelText={intl.formatMessage(messages.emailFieldLabel)}>
|
type='password'
|
||||||
<Input
|
name='password'
|
||||||
type='text'
|
onChange={handleInputChange}
|
||||||
placeholder={intl.formatMessage(messages.emailFieldPlaceholder)}
|
value={password}
|
||||||
name='email'
|
/>
|
||||||
autoComplete='off'
|
</FormGroup>
|
||||||
onChange={handleInputChange}
|
|
||||||
value={email}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={intl.formatMessage(messages.passwordFieldLabel)}>
|
<FormActions>
|
||||||
<Input
|
<Button to='/settings' theme='tertiary'>{intl.formatMessage(messages.cancel)}</Button>
|
||||||
type='password'
|
<Button type='submit' theme='primary' disabled={isLoading}>{intl.formatMessage(messages.submit)}</Button>
|
||||||
name='password'
|
</FormActions>
|
||||||
onChange={handleInputChange}
|
</Form>
|
||||||
value={password}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button to='/settings' theme='tertiary'>{intl.formatMessage(messages.cancel)}</Button>
|
|
||||||
<Button type='submit' theme='primary' disabled={isLoading}>{intl.formatMessage(messages.submit)}</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { changePassword } from 'soapbox/actions/security';
|
import { changePassword } from 'soapbox/actions/security';
|
||||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
import { Button, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
@ -55,57 +55,49 @@ const EditPassword = () => {
|
||||||
}, [currentPassword, newPassword, newPasswordConfirmation, dispatch, intl]);
|
}, [currentPassword, newPassword, newPasswordConfirmation, dispatch, intl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.header)} transparent withHeader={false}>
|
<Column label={intl.formatMessage(messages.header)} backHref='/settings'>
|
||||||
<Card variant='rounded'>
|
<Form onSubmit={handleSubmit}>
|
||||||
<CardHeader backHref='/settings'>
|
<FormGroup labelText={intl.formatMessage(messages.oldPasswordFieldLabel)}>
|
||||||
<CardTitle title={intl.formatMessage(messages.header)} />
|
<Input
|
||||||
</CardHeader>
|
type='password'
|
||||||
|
name='currentPassword'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={currentPassword}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<CardBody>
|
<FormGroup labelText={intl.formatMessage(messages.newPasswordFieldLabel)}>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Input
|
||||||
<FormGroup labelText={intl.formatMessage(messages.oldPasswordFieldLabel)}>
|
type='password'
|
||||||
<Input
|
name='newPassword'
|
||||||
type='password'
|
onChange={handleInputChange}
|
||||||
name='currentPassword'
|
value={newPassword}
|
||||||
onChange={handleInputChange}
|
/>
|
||||||
value={currentPassword}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup labelText={intl.formatMessage(messages.newPasswordFieldLabel)}>
|
{passwordRequirements && (
|
||||||
<Input
|
<PasswordIndicator password={newPassword} onChange={setHasValidPassword} />
|
||||||
type='password'
|
)}
|
||||||
name='newPassword'
|
</FormGroup>
|
||||||
onChange={handleInputChange}
|
|
||||||
value={newPassword}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{passwordRequirements && (
|
<FormGroup labelText={intl.formatMessage(messages.confirmationFieldLabel)}>
|
||||||
<PasswordIndicator password={newPassword} onChange={setHasValidPassword} />
|
<Input
|
||||||
)}
|
type='password'
|
||||||
</FormGroup>
|
name='newPasswordConfirmation'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={newPasswordConfirmation}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup labelText={intl.formatMessage(messages.confirmationFieldLabel)}>
|
<FormActions>
|
||||||
<Input
|
<Button to='/settings' theme='tertiary'>
|
||||||
type='password'
|
{intl.formatMessage(messages.cancel)}
|
||||||
name='newPasswordConfirmation'
|
</Button>
|
||||||
onChange={handleInputChange}
|
|
||||||
value={newPasswordConfirmation}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
<Button type='submit' theme='primary' disabled={isLoading || !hasValidPassword}>
|
||||||
<Button to='/settings' theme='tertiary'>
|
{intl.formatMessage(messages.submit)}
|
||||||
{intl.formatMessage(messages.cancel)}
|
</Button>
|
||||||
</Button>
|
</FormActions>
|
||||||
|
</Form>
|
||||||
<Button type='submit' theme='primary' disabled={isLoading || !hasValidPassword}>
|
|
||||||
{intl.formatMessage(messages.submit)}
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,10 +76,6 @@ const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const navigateToEdit = () => history.push(`/group/${group.slug}/manage/edit`);
|
|
||||||
const navigateToPending = () => history.push(`/group/${group.slug}/manage/requests`);
|
|
||||||
const navigateToBlocks = () => history.push(`/group/${group.slug}/manage/blocks`);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/group/${group.slug}`}>
|
<Column label={intl.formatMessage(messages.heading)} backHref={`/group/${group.slug}`}>
|
||||||
<CardBody className='space-y-4'>
|
<CardBody className='space-y-4'>
|
||||||
|
@ -90,7 +86,7 @@ const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
<ListItem label={intl.formatMessage(messages.editGroup)} onClick={navigateToEdit}>
|
<ListItem label={intl.formatMessage(messages.editGroup)} to={`/group/${group.slug}/manage/edit`}>
|
||||||
<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
|
<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
@ -103,10 +99,10 @@ const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
{backend.software !== TRUTHSOCIAL && (
|
{backend.software !== TRUTHSOCIAL && (
|
||||||
<ListItem label={intl.formatMessage(messages.pendingRequests)} onClick={navigateToPending} />
|
<ListItem label={intl.formatMessage(messages.pendingRequests)} to={`/group/${group.slug}/manage/requests`} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListItem label={intl.formatMessage(messages.blockedMembers)} onClick={navigateToBlocks} />
|
<ListItem label={intl.formatMessage(messages.blockedMembers)} to={`/group/${group.slug}/manage/blocks`} />
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Avatar, Button, CardTitle, Stack } from 'soapbox/components/ui';
|
import { Avatar, Button, CardTitle, Stack } from 'soapbox/components/ui';
|
||||||
import { type Card as StatusCard } from 'soapbox/types/entities';
|
import { type Card as StatusCard } from 'soapbox/types/entities';
|
||||||
|
@ -9,13 +8,9 @@ interface IGroupLinkPreview {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const { group } = card;
|
const { group } = card;
|
||||||
if (!group) return null;
|
if (!group) return null;
|
||||||
|
|
||||||
const navigateToGroup = () => history.push(`/group/${group.slug}`);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className='cursor-default overflow-hidden rounded-lg border border-gray-300 text-center dark:border-gray-800'>
|
<Stack className='cursor-default overflow-hidden rounded-lg border border-gray-300 text-center dark:border-gray-800'>
|
||||||
<div
|
<div
|
||||||
|
@ -32,7 +27,7 @@ const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
||||||
<Stack space={4} className='p-4'>
|
<Stack space={4} className='p-4'>
|
||||||
<CardTitle title={<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />} />
|
<CardTitle title={<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />} />
|
||||||
|
|
||||||
<Button theme='primary' onClick={navigateToGroup} block>
|
<Button theme='primary' to={`/group/${group.slug}`} block>
|
||||||
View Group
|
View Group
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -40,4 +35,4 @@ const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { GroupLinkPreview };
|
export { GroupLinkPreview };
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { fetchMfa } from 'soapbox/actions/mfa';
|
import { fetchMfa } from 'soapbox/actions/mfa';
|
||||||
import List, { ListItem } from 'soapbox/components/list';
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
@ -38,27 +37,12 @@ const messages = defineMessages({
|
||||||
/** User settings page. */
|
/** User settings page. */
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const history = useHistory();
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const mfa = useAppSelector((state) => state.security.get('mfa'));
|
const mfa = useAppSelector((state) => state.security.get('mfa'));
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
|
|
||||||
const navigateToChangeEmail = () => history.push('/settings/email');
|
|
||||||
const navigateToChangePassword = () => history.push('/settings/password');
|
|
||||||
const navigateToMfa = () => history.push('/settings/mfa');
|
|
||||||
const navigateToSessions = () => history.push('/settings/tokens');
|
|
||||||
const navigateToEditProfile = () => history.push('/settings/profile');
|
|
||||||
const navigateToDeleteAccount = () => history.push('/settings/account');
|
|
||||||
const navigateToMoveAccount = () => history.push('/settings/migration');
|
|
||||||
const navigateToAliases = () => history.push('/settings/aliases');
|
|
||||||
const navigateToBackups = () => history.push('/settings/backups');
|
|
||||||
const navigateToImportData = () => history.push('/settings/import');
|
|
||||||
const navigateToExportData = () => history.push('/settings/export');
|
|
||||||
const navigateToMutes = () => history.push('/mutes');
|
|
||||||
const navigateToBlocks = () => history.push('/blocks');
|
|
||||||
|
|
||||||
const isMfaEnabled = mfa.getIn(['settings', 'totp']);
|
const isMfaEnabled = mfa.getIn(['settings', 'totp']);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -78,7 +62,7 @@ const Settings = () => {
|
||||||
|
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<List>
|
<List>
|
||||||
<ListItem label={intl.formatMessage(messages.editProfile)} onClick={navigateToEditProfile}>
|
<ListItem label={intl.formatMessage(messages.editProfile)} to='/settings/profile'>
|
||||||
<span className='max-w-full truncate'>{displayName}</span>
|
<span className='max-w-full truncate'>{displayName}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
@ -90,8 +74,8 @@ const Settings = () => {
|
||||||
|
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<List>
|
<List>
|
||||||
<ListItem label={intl.formatMessage(messages.mutes)} onClick={navigateToMutes} />
|
<ListItem label={intl.formatMessage(messages.mutes)} to='/mutes' />
|
||||||
<ListItem label={intl.formatMessage(messages.blocks)} onClick={navigateToBlocks} />
|
<ListItem label={intl.formatMessage(messages.blocks)} to='/blocks' />
|
||||||
</List>
|
</List>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
|
@ -105,9 +89,9 @@ const Settings = () => {
|
||||||
<List>
|
<List>
|
||||||
{features.security && (
|
{features.security && (
|
||||||
<>
|
<>
|
||||||
<ListItem label={intl.formatMessage(messages.changeEmail)} onClick={navigateToChangeEmail} />
|
<ListItem label={intl.formatMessage(messages.changeEmail)} to='/settings/email' />
|
||||||
<ListItem label={intl.formatMessage(messages.changePassword)} onClick={navigateToChangePassword} />
|
<ListItem label={intl.formatMessage(messages.changePassword)} to='/settings/password' />
|
||||||
<ListItem label={intl.formatMessage(messages.configureMfa)} onClick={navigateToMfa}>
|
<ListItem label={intl.formatMessage(messages.configureMfa)} to='/settings/mfa'>
|
||||||
<span>
|
<span>
|
||||||
{isMfaEnabled ?
|
{isMfaEnabled ?
|
||||||
intl.formatMessage(messages.mfaEnabled) :
|
intl.formatMessage(messages.mfaEnabled) :
|
||||||
|
@ -117,7 +101,7 @@ const Settings = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{features.sessions && (
|
{features.sessions && (
|
||||||
<ListItem label={intl.formatMessage(messages.sessions)} onClick={navigateToSessions} />
|
<ListItem label={intl.formatMessage(messages.sessions)} to='/settings/tokens' />
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
@ -153,25 +137,25 @@ const Settings = () => {
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<List>
|
<List>
|
||||||
{features.importData && (
|
{features.importData && (
|
||||||
<ListItem label={intl.formatMessage(messages.importData)} onClick={navigateToImportData} />
|
<ListItem label={intl.formatMessage(messages.importData)} to='/settings/import' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{features.exportData && (
|
{features.exportData && (
|
||||||
<ListItem label={intl.formatMessage(messages.exportData)} onClick={navigateToExportData} />
|
<ListItem label={intl.formatMessage(messages.exportData)} to='/settings/export' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{features.backups && (
|
{features.backups && (
|
||||||
<ListItem label={intl.formatMessage(messages.backups)} onClick={navigateToBackups} />
|
<ListItem label={intl.formatMessage(messages.backups)} to='/settings/backups' />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{features.federating && (features.accountMoving ? (
|
{features.federating && (features.accountMoving ? (
|
||||||
<ListItem label={intl.formatMessage(messages.accountMigration)} onClick={navigateToMoveAccount} />
|
<ListItem label={intl.formatMessage(messages.accountMigration)} to='/settings/migrations' />
|
||||||
) : features.accountAliases && (
|
) : features.accountAliases && (
|
||||||
<ListItem label={intl.formatMessage(messages.accountAliases)} onClick={navigateToAliases} />
|
<ListItem label={intl.formatMessage(messages.accountAliases)} to='/settings/aliases' />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{features.security && (
|
{features.security && (
|
||||||
<ListItem label={<Text theme='danger'>{intl.formatMessage(messages.deleteAccount)}</Text>} onClick={navigateToDeleteAccount} />
|
<ListItem label={<Text theme='danger'>{intl.formatMessage(messages.deleteAccount)}</Text>} to='/settings/account' />
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { updateSoapboxConfig } from 'soapbox/actions/admin';
|
import { updateSoapboxConfig } from 'soapbox/actions/admin';
|
||||||
import { uploadMedia } from 'soapbox/actions/media';
|
import { uploadMedia } from 'soapbox/actions/media';
|
||||||
|
@ -70,7 +69,6 @@ const templates: Record<string, Template> = {
|
||||||
|
|
||||||
const SoapboxConfig: React.FC = () => {
|
const SoapboxConfig: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
@ -83,8 +81,6 @@ const SoapboxConfig: React.FC = () => {
|
||||||
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(initialData, null, 2));
|
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(initialData, null, 2));
|
||||||
const [jsonValid, setJsonValid] = useState(true);
|
const [jsonValid, setJsonValid] = useState(true);
|
||||||
|
|
||||||
const navigateToThemeEditor = () => history.push('/soapbox/admin/theme');
|
|
||||||
|
|
||||||
const soapbox = useMemo(() => {
|
const soapbox = useMemo(() => {
|
||||||
return normalizeSoapboxConfig(data);
|
return normalizeSoapboxConfig(data);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
@ -211,7 +207,7 @@ const SoapboxConfig: React.FC = () => {
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
label={<FormattedMessage id='soapbox_config.fields.edit_theme_label' defaultMessage='Edit theme' />}
|
label={<FormattedMessage id='soapbox_config.fields.edit_theme_label' defaultMessage='Edit theme' />}
|
||||||
onClick={navigateToThemeEditor}
|
to='/soapbox/admin/theme'
|
||||||
/>
|
/>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import { HStack, Text, Emoji } from 'soapbox/components/ui';
|
import { HStack, Text, Emoji } from 'soapbox/components/ui';
|
||||||
|
@ -17,8 +17,6 @@ interface IStatusInteractionBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.Element | null => {
|
const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.Element | null => {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const me = useAppSelector(({ me }) => me);
|
const me = useAppSelector(({ me }) => me);
|
||||||
const { allowedEmoji } = useSoapboxConfig();
|
const { allowedEmoji } = useSoapboxConfig();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -91,16 +89,10 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToQuotes: React.EventHandler<React.MouseEvent> = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status.id}/quotes`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getQuotes = () => {
|
const getQuotes = () => {
|
||||||
if (status.quotes_count) {
|
if (status.quotes_count) {
|
||||||
return (
|
return (
|
||||||
<InteractionCounter count={status.quotes_count} onClick={navigateToQuotes}>
|
<InteractionCounter count={status.quotes_count} to={`/@${status.getIn(['account', 'acct'])}/posts/${status.id}/quotes`}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='status.interactions.quotes'
|
id='status.interactions.quotes'
|
||||||
defaultMessage='{count, plural, one {Quote} other {Quotes}}'
|
defaultMessage='{count, plural, one {Quote} other {Quotes}}'
|
||||||
|
@ -209,34 +201,47 @@ const StatusInteractionBar: React.FC<IStatusInteractionBar> = ({ status }): JSX.
|
||||||
|
|
||||||
interface IInteractionCounter {
|
interface IInteractionCounter {
|
||||||
count: number
|
count: number
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
to?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const InteractionCounter: React.FC<IInteractionCounter> = ({ count, onClick, children }) => {
|
const InteractionCounter: React.FC<IInteractionCounter> = ({ count, children, onClick, to }) => {
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
|
const className = clsx({
|
||||||
|
'text-gray-600 dark:text-gray-700': true,
|
||||||
|
'hover:underline': features.exposableReactions,
|
||||||
|
'cursor-default': !features.exposableReactions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = (
|
||||||
|
<HStack space={1} alignItems='center'>
|
||||||
|
<Text weight='bold'>
|
||||||
|
{shortNumberFormat(count)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text tag='div' theme='muted'>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
return (
|
||||||
|
<Link to={to} className={className}>
|
||||||
|
{body}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={
|
className={className}
|
||||||
clsx({
|
|
||||||
'text-gray-600 dark:text-gray-700': true,
|
|
||||||
'hover:underline': features.exposableReactions,
|
|
||||||
'cursor-default': !features.exposableReactions,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<HStack space={1} alignItems='center'>
|
{body}
|
||||||
<Text weight='bold'>
|
|
||||||
{shortNumberFormat(count)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text tag='div' theme='muted'>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue