kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
TS, FC
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>environments/review-develop-3zknud/deployments/115^2
rodzic
9b5c342a27
commit
a3d1d2dc91
|
@ -1,101 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { otpVerify, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
|
||||||
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
otpCodeHint: { id: 'login.fields.otp_code_hint', defaultMessage: 'Enter the two-factor code generated by your phone app or use one of your recovery codes' },
|
|
||||||
otpCodeLabel: { id: 'login.fields.otp_code_label', defaultMessage: 'Two-factor code:' },
|
|
||||||
otpLoginFail: { id: 'login.otp_log_in.fail', defaultMessage: 'Invalid code, please try again.' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect()
|
|
||||||
@injectIntl
|
|
||||||
class OtpAuthForm extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
isLoading: false,
|
|
||||||
code_error: '',
|
|
||||||
shouldRedirect: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
mfa_token: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
getFormData = (form) => {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Array.from(form).map(i => [i.name, i.value]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = (event) => {
|
|
||||||
const { dispatch, mfa_token } = this.props;
|
|
||||||
const { code } = this.getFormData(event.target);
|
|
||||||
dispatch(otpVerify(code, mfa_token)).then(({ access_token }) => {
|
|
||||||
this.setState({ code_error: false });
|
|
||||||
return dispatch(verifyCredentials(access_token));
|
|
||||||
}).then(account => {
|
|
||||||
this.setState({ shouldRedirect: true });
|
|
||||||
return dispatch(switchAccount(account.id));
|
|
||||||
}).catch(error => {
|
|
||||||
this.setState({ isLoading: false, code_error: true });
|
|
||||||
});
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl } = this.props;
|
|
||||||
const { code_error, shouldRedirect } = this.state;
|
|
||||||
|
|
||||||
if (shouldRedirect) return <Redirect to='/' />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className='pb-4 sm:pb-10 mb-4 border-b border-gray-200 border-solid -mx-4 sm:-mx-10'>
|
|
||||||
<h1 className='text-center font-bold text-2xl'>
|
|
||||||
<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
|
|
||||||
<Form onSubmit={this.handleSubmit}>
|
|
||||||
<FormGroup
|
|
||||||
labelText={intl.formatMessage(messages.otpCodeLabel)}
|
|
||||||
hintText={intl.formatMessage(messages.otpCodeHint)}
|
|
||||||
errors={code_error ? [intl.formatMessage(messages.otpLoginFail)] : []}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
name='code'
|
|
||||||
type='text'
|
|
||||||
autoComplete='off'
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
autoFocus
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button
|
|
||||||
theme='primary'
|
|
||||||
type='submit'
|
|
||||||
disabled={this.state.isLoading}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { otpVerify, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
|
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
otpCodeHint: { id: 'login.fields.otp_code_hint', defaultMessage: 'Enter the two-factor code generated by your phone app or use one of your recovery codes' },
|
||||||
|
otpCodeLabel: { id: 'login.fields.otp_code_label', defaultMessage: 'Two-factor code:' },
|
||||||
|
otpLoginFail: { id: 'login.otp_log_in.fail', defaultMessage: 'Invalid code, please try again.' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IOtpAuthForm {
|
||||||
|
mfa_token: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const OtpAuthForm: React.FC<IOtpAuthForm> = ({ mfa_token }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [shouldRedirect, setShouldRedirect] = useState(false);
|
||||||
|
const [codeError, setCodeError] = useState<string | boolean>('');
|
||||||
|
|
||||||
|
const getFormData = (form: any) => Object.fromEntries(
|
||||||
|
Array.from(form).map((i: any) => [i.name, i.value]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = (event: React.FormEvent<Element>) => {
|
||||||
|
const { code } = getFormData(event.target);
|
||||||
|
dispatch(otpVerify(code, mfa_token)).then(({ access_token }) => {
|
||||||
|
setCodeError(false);
|
||||||
|
return dispatch(verifyCredentials(access_token));
|
||||||
|
}).then(account => {
|
||||||
|
setShouldRedirect(true);
|
||||||
|
return dispatch(switchAccount(account.id));
|
||||||
|
}).catch(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setCodeError(true);
|
||||||
|
});
|
||||||
|
setIsLoading(true);
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldRedirect) return <Redirect to='/' />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='pb-4 sm:pb-10 mb-4 border-b border-gray-200 border-solid -mx-4 sm:-mx-10'>
|
||||||
|
<h1 className='text-center font-bold text-2xl'>
|
||||||
|
<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<FormGroup
|
||||||
|
labelText={intl.formatMessage(messages.otpCodeLabel)}
|
||||||
|
hintText={intl.formatMessage(messages.otpCodeHint)}
|
||||||
|
errors={codeError ? [intl.formatMessage(messages.otpLoginFail)] : []}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
name='code'
|
||||||
|
type='text'
|
||||||
|
autoComplete='off'
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button
|
||||||
|
theme='primary'
|
||||||
|
type='submit'
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OtpAuthForm;
|
|
@ -1,72 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { resetPassword } from 'soapbox/actions/security';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
|
||||||
|
|
||||||
import { Button, Form, FormActions, FormGroup, Input } from '../../../components/ui';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
nicknameOrEmail: { id: 'password_reset.fields.username_placeholder', defaultMessage: 'Email or username' },
|
|
||||||
confirmation: { id: 'password_reset.confirmation', defaultMessage: 'Check your email for confirmation.' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect()
|
|
||||||
@injectIntl
|
|
||||||
class PasswordReset extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
isLoading: false,
|
|
||||||
success: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
const { dispatch, intl } = this.props;
|
|
||||||
const nicknameOrEmail = e.target.nickname_or_email.value;
|
|
||||||
this.setState({ isLoading: true });
|
|
||||||
dispatch(resetPassword(nicknameOrEmail)).then(() => {
|
|
||||||
this.setState({ isLoading: false, success: true });
|
|
||||||
dispatch(snackbar.info(intl.formatMessage(messages.confirmation)));
|
|
||||||
}).catch(error => {
|
|
||||||
this.setState({ isLoading: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl } = this.props;
|
|
||||||
|
|
||||||
if (this.state.success) return <Redirect to='/' />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className='pb-4 sm:pb-10 mb-4 border-b border-gray-200 dark:border-gray-600 border-solid -mx-4 sm:-mx-10'>
|
|
||||||
<h1 className='text-center font-bold text-2xl'>
|
|
||||||
<FormattedMessage id='password_reset.header' defaultMessage='Reset Password' />
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
|
|
||||||
<Form onSubmit={this.handleSubmit}>
|
|
||||||
<FormGroup labelText={intl.formatMessage(messages.nicknameOrEmail)}>
|
|
||||||
<Input
|
|
||||||
name='nickname_or_email'
|
|
||||||
placeholder='me@example.com'
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button type='submit' theme='primary' disabled={this.state.isLoading}>
|
|
||||||
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { resetPassword } from 'soapbox/actions/security';
|
||||||
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
nicknameOrEmail: { id: 'password_reset.fields.username_placeholder', defaultMessage: 'Email or username' },
|
||||||
|
confirmation: { id: 'password_reset.confirmation', defaultMessage: 'Check your email for confirmation.' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const PasswordReset = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<Element>) => {
|
||||||
|
const nicknameOrEmail = (e.target as any).nickname_or_email.value;
|
||||||
|
setIsLoading(true);
|
||||||
|
dispatch(resetPassword(nicknameOrEmail)).then(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setSuccess(true);
|
||||||
|
dispatch(snackbar.info(intl.formatMessage(messages.confirmation)));
|
||||||
|
}).catch(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (success) return <Redirect to='/' />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='pb-4 sm:pb-10 mb-4 border-b border-gray-200 dark:border-gray-600 border-solid -mx-4 sm:-mx-10'>
|
||||||
|
<h1 className='text-center font-bold text-2xl'>
|
||||||
|
<FormattedMessage id='password_reset.header' defaultMessage='Reset Password' />
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<FormGroup labelText={intl.formatMessage(messages.nicknameOrEmail)}>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
name='nickname_or_email'
|
||||||
|
placeholder='me@example.com'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button type='submit' theme='primary' disabled={isLoading}>
|
||||||
|
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PasswordReset;
|
|
@ -1,34 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { openModal } from '../../actions/modals';
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onLoad: (text) => {
|
|
||||||
dispatch(openModal('COMPOSE'));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(null, mapDispatchToProps)
|
|
||||||
class NewStatus extends React.Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onLoad: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.props.onLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Redirect to='/' />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const NewStatus = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(openModal('COMPOSE'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Redirect to='/' />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewStatus;
|
|
@ -1,8 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { Button, HStack } from 'soapbox/components/ui';
|
import { Button, HStack } from 'soapbox/components/ui';
|
||||||
import { useSettings } from 'soapbox/hooks';
|
import { useSettings } from 'soapbox/hooks';
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { fetchAccount } from 'soapbox/actions/accounts';
|
|
||||||
import { addToMentions, removeFromMentions } from 'soapbox/actions/compose';
|
|
||||||
import Avatar from 'soapbox/components/avatar';
|
|
||||||
import DisplayName from 'soapbox/components/display-name';
|
|
||||||
import IconButton from 'soapbox/components/icon_button';
|
|
||||||
import { makeGetAccount } from 'soapbox/selectors';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
remove: { id: 'reply_mentions.account.remove', defaultMessage: 'Remove from mentions' },
|
|
||||||
add: { id: 'reply_mentions.account.add', defaultMessage: 'Add to mentions' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => {
|
|
||||||
const account = getAccount(state, accountId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
added: !!account && state.getIn(['compose', 'to']).includes(account.get('acct')),
|
|
||||||
account,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
|
||||||
onRemove: () => dispatch(removeFromMentions(accountId)),
|
|
||||||
onAdd: () => dispatch(addToMentions(accountId)),
|
|
||||||
fetchAccount: () => dispatch(fetchAccount(accountId)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(makeMapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class Account extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
accountId: PropTypes.string.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onRemove: PropTypes.func.isRequired,
|
|
||||||
onAdd: PropTypes.func.isRequired,
|
|
||||||
added: PropTypes.bool,
|
|
||||||
author: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
added: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { account, accountId } = this.props;
|
|
||||||
|
|
||||||
if (accountId && !account) {
|
|
||||||
this.props.fetchAccount(accountId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account, intl, onRemove, onAdd, added, author } = this.props;
|
|
||||||
|
|
||||||
if (!account) return null;
|
|
||||||
|
|
||||||
let button;
|
|
||||||
|
|
||||||
if (added) {
|
|
||||||
button = <IconButton src={require('@tabler/icons/icons/x.svg')} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
|
||||||
} else {
|
|
||||||
button = <IconButton src={require('@tabler/icons/icons/plus.svg')} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account'>
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<div className='account__display-name'>
|
|
||||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__relationship'>
|
|
||||||
{!author && button}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { fetchAccount } from 'soapbox/actions/accounts';
|
||||||
|
import { addToMentions, removeFromMentions } from 'soapbox/actions/compose';
|
||||||
|
import Avatar from 'soapbox/components/avatar';
|
||||||
|
import DisplayName from 'soapbox/components/display-name';
|
||||||
|
import IconButton from 'soapbox/components/icon_button';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
remove: { id: 'reply_mentions.account.remove', defaultMessage: 'Remove from mentions' },
|
||||||
|
add: { id: 'reply_mentions.account.add', defaultMessage: 'Add to mentions' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
interface IAccount {
|
||||||
|
accountId: string,
|
||||||
|
author: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Account: React.FC<IAccount> = ({ accountId, author }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||||
|
const added = useAppSelector((state) => !!account && state.compose.get('to').includes(account.acct));
|
||||||
|
|
||||||
|
const onRemove = () => dispatch(removeFromMentions(accountId));
|
||||||
|
const onAdd = () => dispatch(addToMentions(accountId));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (accountId && !account) {
|
||||||
|
dispatch(fetchAccount(accountId));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
let button;
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
button = <IconButton src={require('@tabler/icons/icons/x.svg')} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||||
|
} else {
|
||||||
|
button = <IconButton src={require('@tabler/icons/icons/plus.svg')} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__relationship'>
|
||||||
|
{!author && button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Account;
|
|
@ -1,48 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import Column from '../ui/components/column';
|
|
||||||
import LinkFooter from '../ui/components/link_footer';
|
|
||||||
import PromoPanel from '../ui/components/promo_panel';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
heading: { id: 'column.info', defaultMessage: 'Server information' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
instance: state.get('instance'),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class ServerInfo extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl, instance } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column icon='info' label={intl.formatMessage(messages.heading)}>
|
|
||||||
<div className='info_column_area'>
|
|
||||||
<div className='info__brand'>
|
|
||||||
<div className='brand'>
|
|
||||||
<h1>{instance.get('title')}</h1>
|
|
||||||
</div>
|
|
||||||
<div className='brand__tagline'>
|
|
||||||
<span>{instance.get('description')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PromoPanel />
|
|
||||||
<LinkFooter />
|
|
||||||
</div>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import Column from '../ui/components/column';
|
||||||
|
import LinkFooter from '../ui/components/link_footer';
|
||||||
|
import PromoPanel from '../ui/components/promo_panel';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.info', defaultMessage: 'Server information' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ServerInfo = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const instance = useAppSelector((state) => state.instance);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column icon='info' label={intl.formatMessage(messages.heading)}>
|
||||||
|
<div className='info_column_area'>
|
||||||
|
<div className='info__brand'>
|
||||||
|
<div className='brand'>
|
||||||
|
<h1>{instance.title}</h1>
|
||||||
|
</div>
|
||||||
|
<div className='brand__tagline'>
|
||||||
|
<span>{instance.description}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PromoPanel />
|
||||||
|
<LinkFooter />
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerInfo;
|
|
@ -1,16 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import { getSettings, changeSettingImmediate } from 'soapbox/actions/settings';
|
import { getSettings, changeSettingImmediate } from 'soapbox/actions/settings';
|
||||||
import {
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
SimpleForm,
|
import { Card, CardBody, CardHeader, CardTitle } from 'soapbox/components/ui';
|
||||||
SelectDropdown,
|
import { SimpleForm, SelectDropdown } from 'soapbox/features/forms';
|
||||||
} from 'soapbox/features/forms';
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
|
||||||
|
|
||||||
import List, { ListItem } from '../../components/list';
|
|
||||||
import { Card, CardBody, CardHeader, CardTitle } from '../../components/ui';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
mediaDisplay: { id: 'preferences.fields.media_display_label', defaultMessage: 'Media display' },
|
mediaDisplay: { id: 'preferences.fields.media_display_label', defaultMessage: 'Media display' },
|
||||||
|
@ -20,7 +15,7 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const MediaDisplay = () => {
|
const MediaDisplay = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const settings = useAppSelector((state) => getSettings(state));
|
const settings = useAppSelector((state) => getSettings(state));
|
||||||
|
@ -31,7 +26,7 @@ const MediaDisplay = () => {
|
||||||
show_all: intl.formatMessage(messages.display_media_show_all),
|
show_all: intl.formatMessage(messages.display_media_show_all),
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectChange = path => {
|
const onSelectChange: (path: string[]) => React.ChangeEventHandler<HTMLSelectElement> = path => {
|
||||||
return e => {
|
return e => {
|
||||||
dispatch(changeSettingImmediate(path, e.target.value));
|
dispatch(changeSettingImmediate(path, e.target.value));
|
||||||
};
|
};
|
||||||
|
@ -49,7 +44,7 @@ const MediaDisplay = () => {
|
||||||
<ListItem label={intl.formatMessage(messages.mediaDisplay)}>
|
<ListItem label={intl.formatMessage(messages.mediaDisplay)}>
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
items={displayMediaOptions}
|
items={displayMediaOptions}
|
||||||
defaultValue={settings.get('displayMedia')}
|
defaultValue={settings.get('displayMedia') as string}
|
||||||
onChange={onSelectChange(['displayMedia'])}
|
onChange={onSelectChange(['displayMedia'])}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
|
@ -1,47 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { openComposeWithText } from '../../actions/compose';
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onShare: (text) => {
|
|
||||||
dispatch(openComposeWithText(text));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(null, mapDispatchToProps)
|
|
||||||
class Share extends React.Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onShare: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
const text = [
|
|
||||||
params.get('title'),
|
|
||||||
params.get('text'),
|
|
||||||
params.get('url'),
|
|
||||||
]
|
|
||||||
.filter(v => v)
|
|
||||||
.join('\n\n');
|
|
||||||
|
|
||||||
if (text) {
|
|
||||||
this.props.onShare(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Redirect to='/' />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Redirect, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { openComposeWithText } from 'soapbox/actions/compose';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const Share = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { search } = useLocation();
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
params.get('title'),
|
||||||
|
params.get('text'),
|
||||||
|
params.get('url'),
|
||||||
|
]
|
||||||
|
.filter(v => v)
|
||||||
|
.join('\n\n');
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
dispatch(openComposeWithText(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Redirect to='/' />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Share;
|
|
@ -49,7 +49,6 @@ const SitePreview: React.FC<ISitePreview> = ({ soapbox }) => {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<SiteLogo alt='Logo' className='h-5 lg:h-6 w-auto self-center px-2' theme={dark ? 'dark' : 'light'} />
|
<SiteLogo alt='Logo' className='h-5 lg:h-6 w-auto self-center px-2' theme={dark ? 'dark' : 'light'} />
|
||||||
{/* <img alt='Logo' className='h-5 lg:h-6 self-center px-2' src={soapboxConfig.logo} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,7 +33,7 @@ const ReplyMentionsModal: React.FC<IReplyMentionsModal> = ({ onClose }) => {
|
||||||
closePosition='left'
|
closePosition='left'
|
||||||
>
|
>
|
||||||
<div className='reply-mentions-modal__accounts'>
|
<div className='reply-mentions-modal__accounts'>
|
||||||
{mentions.map(accountId => <Account key={accountId} accountId={accountId} added author={author === accountId} />)}
|
{mentions.map(accountId => <Account key={accountId} accountId={accountId} author={author === accountId} />)}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import { AxiosError } from 'axios';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { confirmEmailVerification } from 'soapbox/actions/verification';
|
import { confirmEmailVerification } from 'soapbox/actions/verification';
|
||||||
|
@ -91,8 +92,8 @@ const TokenExpired = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmailPassThru = ({ match }) => {
|
const EmailPassThru = () => {
|
||||||
const { token } = match.params;
|
const { token } = useParams<{ token: string }>();
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -106,7 +107,7 @@ const EmailPassThru = ({ match }) => {
|
||||||
setStatus(Statuses.SUCCESS);
|
setStatus(Statuses.SUCCESS);
|
||||||
dispatch(snackbar.success(intl.formatMessage({ id: 'email_passthru.success', defaultMessage: 'Your email has been verified!' })));
|
dispatch(snackbar.success(intl.formatMessage({ id: 'email_passthru.success', defaultMessage: 'Your email has been verified!' })));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error: AxiosError<any>) => {
|
||||||
const errorKey = error?.response?.data?.error;
|
const errorKey = error?.response?.data?.error;
|
||||||
let message = intl.formatMessage({
|
let message = intl.formatMessage({
|
||||||
id: 'email_passthru.fail.generic',
|
id: 'email_passthru.fail.generic',
|
||||||
|
@ -155,8 +156,4 @@ const EmailPassThru = ({ match }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailPassThru.propTypes = {
|
|
||||||
match: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmailPassThru;
|
export default EmailPassThru;
|
|
@ -1,4 +1,3 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
@ -12,15 +11,15 @@ import { useAppSelector, useOwnAccount } from 'soapbox/hooks';
|
||||||
import { logOut } from '../../actions/auth';
|
import { logOut } from '../../actions/auth';
|
||||||
import { Button, Stack, Text } from '../../components/ui';
|
import { Button, Stack, Text } from '../../components/ui';
|
||||||
|
|
||||||
const WaitlistPage = ({ account }) => {
|
const WaitlistPage = (/* { account } */) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const title = useAppSelector((state) => state.instance.title);
|
const title = useAppSelector((state) => state.instance.title);
|
||||||
|
|
||||||
const me = useOwnAccount();
|
const me = useOwnAccount();
|
||||||
const isSmsVerified = me.getIn(['source', 'sms_verified']);
|
const isSmsVerified = me?.source.get('sms_verified');
|
||||||
|
|
||||||
const onClickLogOut = (event) => {
|
const onClickLogOut: React.MouseEventHandler = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dispatch(logOut(intl));
|
dispatch(logOut(intl));
|
||||||
};
|
};
|
||||||
|
@ -76,8 +75,4 @@ const WaitlistPage = ({ account }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
WaitlistPage.propTypes = {
|
|
||||||
account: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WaitlistPage;
|
export default WaitlistPage;
|
Ładowanie…
Reference in New Issue