kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'login-ui' into 'main'
Move Login pages into the UI, delete AuthLayout See merge request soapbox-pub/soapbox!2730environments/review-main-yi2y9f/deployments/3980
commit
0d7c595818
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Card, CardBody, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
interface IBigCard {
|
||||||
|
title: React.ReactNode
|
||||||
|
subtitle?: React.ReactNode
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const BigCard: React.FC<IBigCard> = ({ title, subtitle, children }) => {
|
||||||
|
return (
|
||||||
|
<Card variant='rounded' size='xl'>
|
||||||
|
<CardBody>
|
||||||
|
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
||||||
|
<Stack space={2}>
|
||||||
|
<Text size='2xl' align='center' weight='bold'>{title}</Text>
|
||||||
|
{subtitle && <Text theme='muted' align='center'>{subtitle}</Text>}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stack space={5}>
|
||||||
|
<div className='mx-auto sm:w-2/3 sm:pt-10'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { BigCard };
|
|
@ -17,7 +17,6 @@ import GdprBanner from 'soapbox/components/gdpr-banner';
|
||||||
import Helmet from 'soapbox/components/helmet';
|
import Helmet from 'soapbox/components/helmet';
|
||||||
import LoadingScreen from 'soapbox/components/loading-screen';
|
import LoadingScreen from 'soapbox/components/loading-screen';
|
||||||
import { StatProvider } from 'soapbox/contexts/stat-context';
|
import { StatProvider } from 'soapbox/contexts/stat-context';
|
||||||
import AuthLayout from 'soapbox/features/auth-layout';
|
|
||||||
import EmbeddedStatus from 'soapbox/features/embedded-status';
|
import EmbeddedStatus from 'soapbox/features/embedded-status';
|
||||||
import PublicLayout from 'soapbox/features/public-layout';
|
import PublicLayout from 'soapbox/features/public-layout';
|
||||||
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
||||||
|
@ -104,11 +103,6 @@ const SoapboxMount = () => {
|
||||||
<Route exact path='/' component={PublicLayout} />
|
<Route exact path='/' component={PublicLayout} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route path='/login' component={AuthLayout} />
|
|
||||||
<Route path='/reset-password' component={AuthLayout} />
|
|
||||||
<Route path='/edit-password' component={AuthLayout} />
|
|
||||||
<Route path='/invite/:token' component={AuthLayout} />
|
|
||||||
|
|
||||||
<Route path='/' component={UI} />
|
<Route path='/' component={UI} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
|
||||||
import { Link, Redirect, Route, Switch, useHistory, useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
|
||||||
import SiteLogo from 'soapbox/components/site-logo';
|
|
||||||
import { useOwnAccount, useInstance, useRegistrationStatus } from 'soapbox/hooks';
|
|
||||||
|
|
||||||
import { Button, Card, CardBody } from '../../components/ui';
|
|
||||||
import LoginPage from '../auth-login/components/login-page';
|
|
||||||
import PasswordReset from '../auth-login/components/password-reset';
|
|
||||||
import PasswordResetConfirm from '../auth-login/components/password-reset-confirm';
|
|
||||||
import ExternalLoginForm from '../external-login/components/external-login-form';
|
|
||||||
import Footer from '../public-layout/components/footer';
|
|
||||||
import RegisterInvite from '../register-invite';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
register: { id: 'auth_layout.register', defaultMessage: 'Create an account' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const AuthLayout = () => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const history = useHistory();
|
|
||||||
const { search } = useLocation();
|
|
||||||
|
|
||||||
const { account } = useOwnAccount();
|
|
||||||
const instance = useInstance();
|
|
||||||
const { isOpen } = useRegistrationStatus();
|
|
||||||
const isLoginPage = history.location.pathname === '/login';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='h-full'>
|
|
||||||
<LandingGradient />
|
|
||||||
|
|
||||||
<main className='relative h-full sm:flex sm:justify-center'>
|
|
||||||
<div className='flex h-full w-full flex-col sm:max-w-lg md:max-w-2xl lg:max-w-6xl'>
|
|
||||||
<header className='relative mb-auto flex justify-between px-2 py-12'>
|
|
||||||
<div className='relative z-0 flex-1 px-2 lg:absolute lg:inset-0 lg:flex lg:items-center lg:justify-center'>
|
|
||||||
<Link to='/' className='cursor-pointer'>
|
|
||||||
<SiteLogo alt={instance.title} className='h-7' />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(isLoginPage && isOpen) && (
|
|
||||||
<div className='relative z-10 ml-auto flex items-center'>
|
|
||||||
<Button
|
|
||||||
theme='tertiary'
|
|
||||||
icon={require('@tabler/icons/user.svg')}
|
|
||||||
to='/signup'
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.register)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className='flex flex-col items-center justify-center'>
|
|
||||||
<div className='w-full pb-10 sm:mx-auto sm:max-w-lg md:max-w-2xl'>
|
|
||||||
<Card variant='rounded' size='xl'>
|
|
||||||
<CardBody>
|
|
||||||
<Switch>
|
|
||||||
{/* If already logged in, redirect home. */}
|
|
||||||
{account && <Redirect from='/login' to='/' exact />}
|
|
||||||
|
|
||||||
<Route exact path='/login/external' component={ExternalLoginForm} />
|
|
||||||
<Route exact path='/login/add' component={LoginPage} />
|
|
||||||
<Route exact path='/login' component={LoginPage} />
|
|
||||||
<Route exact path='/reset-password' component={PasswordReset} />
|
|
||||||
<Route exact path='/edit-password' component={PasswordResetConfirm} />
|
|
||||||
<Route path='/invite/:token' component={RegisterInvite} />
|
|
||||||
|
|
||||||
<Redirect from='/auth/password/new' to='/reset-password' />
|
|
||||||
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
|
|
||||||
</Switch>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-auto'>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthLayout;
|
|
|
@ -2,11 +2,9 @@ import React from 'react';
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Button, Form, FormActions, FormGroup, Input, Stack } from 'soapbox/components/ui';
|
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
import { useFeatures } from 'soapbox/hooks';
|
import { useFeatures } from 'soapbox/hooks';
|
||||||
|
|
||||||
import ConsumersList from './consumers-list';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
username: {
|
username: {
|
||||||
id: 'login.fields.username_label',
|
id: 'login.fields.username_label',
|
||||||
|
@ -35,62 +33,52 @@ const LoginForm: React.FC<ILoginForm> = ({ isLoading, handleSubmit }) => {
|
||||||
const passwordLabel = intl.formatMessage(messages.password);
|
const passwordLabel = intl.formatMessage(messages.password);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Form onSubmit={handleSubmit}>
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
<FormGroup labelText={usernameLabel}>
|
||||||
<h1 className='text-center text-2xl font-bold'><FormattedMessage id='login_form.header' defaultMessage='Sign In' /></h1>
|
<Input
|
||||||
</div>
|
aria-label={usernameLabel}
|
||||||
|
placeholder={usernameLabel}
|
||||||
|
type='text'
|
||||||
|
name='username'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoCapitalize='off'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<Stack className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2' space={5}>
|
<FormGroup
|
||||||
<Form onSubmit={handleSubmit}>
|
labelText={passwordLabel}
|
||||||
<FormGroup labelText={usernameLabel}>
|
hintText={
|
||||||
<Input
|
<Link to='/reset-password' className='hover:underline' tabIndex={-1}>
|
||||||
aria-label={usernameLabel}
|
<FormattedMessage
|
||||||
placeholder={usernameLabel}
|
id='login.reset_password_hint'
|
||||||
type='text'
|
defaultMessage='Trouble logging in?'
|
||||||
name='username'
|
|
||||||
autoCorrect='off'
|
|
||||||
autoCapitalize='off'
|
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Link>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
aria-label={passwordLabel}
|
||||||
|
placeholder={passwordLabel}
|
||||||
|
type='password'
|
||||||
|
name='password'
|
||||||
|
autoComplete='off'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoCapitalize='off'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormActions>
|
||||||
labelText={passwordLabel}
|
<Button
|
||||||
hintText={
|
theme='primary'
|
||||||
<Link to='/reset-password' className='hover:underline' tabIndex={-1}>
|
type='submit'
|
||||||
<FormattedMessage
|
disabled={isLoading}
|
||||||
id='login.reset_password_hint'
|
>
|
||||||
defaultMessage='Trouble logging in?'
|
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
||||||
/>
|
</Button>
|
||||||
</Link>
|
</FormActions>
|
||||||
}
|
</Form>
|
||||||
>
|
|
||||||
<Input
|
|
||||||
aria-label={passwordLabel}
|
|
||||||
placeholder={passwordLabel}
|
|
||||||
type='password'
|
|
||||||
name='password'
|
|
||||||
autoComplete='off'
|
|
||||||
autoCorrect='off'
|
|
||||||
autoCapitalize='off'
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button
|
|
||||||
theme='primary'
|
|
||||||
type='submit'
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
<ConsumersList />
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
import { fetchInstance } from 'soapbox/actions/instance';
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
import { closeModal } from 'soapbox/actions/modals';
|
import { closeModal } from 'soapbox/actions/modals';
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
import { getRedirectUrl } from 'soapbox/utils/redirect';
|
import { getRedirectUrl } from 'soapbox/utils/redirect';
|
||||||
import { isStandalone } from 'soapbox/utils/state';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
|
import ConsumersList from './consumers-list';
|
||||||
import LoginForm from './login-form';
|
import LoginForm from './login-form';
|
||||||
import OtpAuthForm from './otp-auth-form';
|
import OtpAuthForm from './otp-auth-form';
|
||||||
|
|
||||||
|
@ -68,7 +71,12 @@ const LoginPage = () => {
|
||||||
|
|
||||||
if (mfaAuthNeeded) return <OtpAuthForm mfa_token={mfaToken} />;
|
if (mfaAuthNeeded) return <OtpAuthForm mfa_token={mfaToken} />;
|
||||||
|
|
||||||
return <LoginForm handleSubmit={handleSubmit} isLoading={isLoading} />;
|
return (
|
||||||
|
<BigCard title={<FormattedMessage id='login_form.header' defaultMessage='Sign In' />}>
|
||||||
|
<LoginForm handleSubmit={handleSubmit} isLoading={isLoading} />
|
||||||
|
<ConsumersList />
|
||||||
|
</BigCard>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { otpVerify, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
import { otpVerify, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -47,41 +48,33 @@ const OtpAuthForm: React.FC<IOtpAuthForm> = ({ mfa_token }) => {
|
||||||
if (shouldRedirect) return <Redirect to='/' />;
|
if (shouldRedirect) return <Redirect to='/' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<BigCard title={<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />}>
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-600 sm:-mx-10 sm:pb-10'>
|
<Form onSubmit={handleSubmit}>
|
||||||
<h1 className='text-center text-2xl font-bold'>
|
<FormGroup
|
||||||
<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />
|
labelText={intl.formatMessage(messages.otpCodeLabel)}
|
||||||
</h1>
|
hintText={intl.formatMessage(messages.otpCodeHint)}
|
||||||
</div>
|
errors={codeError ? [intl.formatMessage(messages.otpLoginFail)] : []}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
name='code'
|
||||||
|
type='text'
|
||||||
|
autoComplete='off'
|
||||||
|
autoFocus
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<FormActions>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Button
|
||||||
<FormGroup
|
theme='primary'
|
||||||
labelText={intl.formatMessage(messages.otpCodeLabel)}
|
type='submit'
|
||||||
hintText={intl.formatMessage(messages.otpCodeHint)}
|
disabled={isLoading}
|
||||||
errors={codeError ? [intl.formatMessage(messages.otpLoginFail)] : []}
|
|
||||||
>
|
>
|
||||||
<Input
|
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
||||||
name='code'
|
</Button>
|
||||||
type='text'
|
</FormActions>
|
||||||
autoComplete='off'
|
</Form>
|
||||||
autoFocus
|
</BigCard>
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button
|
|
||||||
theme='primary'
|
|
||||||
type='submit'
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { resetPasswordConfirm } from 'soapbox/actions/security';
|
import { resetPasswordConfirm } from 'soapbox/actions/security';
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -55,33 +56,25 @@ const PasswordResetConfirm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<BigCard title={<FormattedMessage id='reset_password.header' defaultMessage='Set New Password' />}>
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-600 sm:-mx-10 sm:pb-10'>
|
<Form onSubmit={handleSubmit}>
|
||||||
<h1 className='text-center text-2xl font-bold'>
|
<FormGroup labelText={<FormattedMessage id='reset_password.password.label' defaultMessage='Password' />} errors={renderErrors()}>
|
||||||
<FormattedMessage id='reset_password.header' defaultMessage='Set New Password' />
|
<Input
|
||||||
</h1>
|
type='password'
|
||||||
</div>
|
name='password'
|
||||||
|
placeholder={intl.formatMessage(messages.passwordPlaceholder)}
|
||||||
|
onChange={onChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<FormActions>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Button type='submit' theme='primary' disabled={isLoading}>
|
||||||
<FormGroup labelText={<FormattedMessage id='reset_password.password.label' defaultMessage='Password' />} errors={renderErrors()}>
|
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
||||||
<Input
|
</Button>
|
||||||
type='password'
|
</FormActions>
|
||||||
name='password'
|
</Form>
|
||||||
placeholder={intl.formatMessage(messages.passwordPlaceholder)}
|
</BigCard>
|
||||||
onChange={onChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button type='submit' theme='primary' disabled={isLoading}>
|
|
||||||
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import { resetPassword } from 'soapbox/actions/security';
|
import { resetPassword } from 'soapbox/actions/security';
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
import { Button, 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';
|
||||||
|
@ -36,32 +37,24 @@ const PasswordReset = () => {
|
||||||
if (success) return <Redirect to='/' />;
|
if (success) return <Redirect to='/' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<BigCard title={<FormattedMessage id='password_reset.header' defaultMessage='Reset Password' />}>
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-600 sm:-mx-10 sm:pb-10'>
|
<Form onSubmit={handleSubmit}>
|
||||||
<h1 className='text-center text-2xl font-bold'>
|
<FormGroup labelText={intl.formatMessage(features.logInWithUsername ? messages.nicknameOrEmail : messages.email)}>
|
||||||
<FormattedMessage id='password_reset.header' defaultMessage='Reset Password' />
|
<Input
|
||||||
</h1>
|
type='text'
|
||||||
</div>
|
name='nickname_or_email'
|
||||||
|
placeholder='me@example.com'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<FormActions>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Button type='submit' theme='primary' disabled={isLoading}>
|
||||||
<FormGroup labelText={intl.formatMessage(features.logInWithUsername ? messages.nicknameOrEmail : messages.email)}>
|
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
||||||
<Input
|
</Button>
|
||||||
type='text'
|
</FormActions>
|
||||||
name='nickname_or_email'
|
</Form>
|
||||||
placeholder='me@example.com'
|
</BigCard>
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormActions>
|
|
||||||
<Button type='submit' theme='primary' disabled={isLoading}>
|
|
||||||
<FormattedMessage id='password_reset.reset' defaultMessage='Reset password' />
|
|
||||||
</Button>
|
|
||||||
</FormActions>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Card, CardTitle, Stack } from 'soapbox/components/ui';
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
|
|
||||||
import RegistrationForm from './registration-form';
|
import RegistrationForm from './registration-form';
|
||||||
|
|
||||||
const RegistrationPage: React.FC = () => {
|
const RegistrationPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded'>
|
<BigCard title={<FormattedMessage id='column.registration' defaultMessage='Sign Up' />}>
|
||||||
<Stack space={2}>
|
<RegistrationForm />
|
||||||
<CardTitle title={<FormattedMessage id='column.registration' defaultMessage='Sign Up' />} />
|
</BigCard>
|
||||||
<RegistrationForm />
|
|
||||||
</Stack>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
|
|
||||||
import ExternalLoginForm from './components/external-login-form';
|
import ExternalLoginForm from './components/external-login-form';
|
||||||
|
|
||||||
/** Page for logging into a remote instance */
|
/** Page for logging into a remote instance */
|
||||||
const ExternalLoginPage: React.FC = () => {
|
const ExternalLoginPage: React.FC = () => {
|
||||||
return <ExternalLoginForm />;
|
return (
|
||||||
|
<BigCard title={<FormattedMessage id='login_form.header' defaultMessage='Sign In' />}>
|
||||||
|
<ExternalLoginForm />
|
||||||
|
</BigCard>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ExternalLoginPage;
|
export default ExternalLoginPage;
|
||||||
|
|
|
@ -3,7 +3,8 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
|
import { Avatar, Button, Icon, Spinner, Stack } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
import { isDefaultAvatar } from 'soapbox/utils/accounts';
|
import { isDefaultAvatar } from 'soapbox/utils/accounts';
|
||||||
|
@ -64,69 +65,54 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='xl'>
|
<BigCard
|
||||||
<CardBody>
|
title={<FormattedMessage id='onboarding.avatar.title' defaultMessage='Choose a profile picture' />}
|
||||||
<div>
|
subtitle={<FormattedMessage id='onboarding.avatar.subtitle' defaultMessage='Just have fun with it.' />}
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-900/50 sm:-mx-10 sm:pb-10'>
|
>
|
||||||
<Stack space={2}>
|
<Stack space={10}>
|
||||||
<Text size='2xl' align='center' weight='bold'>
|
<div className='relative mx-auto rounded-full bg-gray-200'>
|
||||||
<FormattedMessage id='onboarding.avatar.title' defaultMessage='Choose a profile picture' />
|
{account && (
|
||||||
</Text>
|
<Avatar src={selectedFile || account.avatar} size={175} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Text theme='muted' align='center'>
|
{isSubmitting && (
|
||||||
<FormattedMessage id='onboarding.avatar.subtitle' defaultMessage='Just have fun with it.' />
|
<div className='absolute inset-0 flex items-center justify-center rounded-full bg-white/80 dark:bg-primary-900/80'>
|
||||||
</Text>
|
<Spinner withText={false} />
|
||||||
</Stack>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<button
|
||||||
<Stack space={10}>
|
onClick={openFilePicker}
|
||||||
<div className='relative mx-auto rounded-full bg-gray-200'>
|
type='button'
|
||||||
{account && (
|
className={clsx({
|
||||||
<Avatar src={selectedFile || account.avatar} size={175} />
|
'absolute bottom-3 right-2 p-1 bg-primary-600 rounded-full ring-2 ring-white dark:ring-primary-900 hover:bg-primary-700': true,
|
||||||
)}
|
'opacity-50 pointer-events-none': isSubmitting,
|
||||||
|
})}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
<Icon src={require('@tabler/icons/plus.svg')} className='h-5 w-5 text-white' />
|
||||||
|
</button>
|
||||||
|
|
||||||
{isSubmitting && (
|
<input type='file' className='hidden' ref={fileInput} onChange={handleFileChange} />
|
||||||
<div className='absolute inset-0 flex items-center justify-center rounded-full bg-white/80 dark:bg-primary-900/80'>
|
|
||||||
<Spinner withText={false} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={openFilePicker}
|
|
||||||
type='button'
|
|
||||||
className={clsx({
|
|
||||||
'absolute bottom-3 right-2 p-1 bg-primary-600 rounded-full ring-2 ring-white dark:ring-primary-900 hover:bg-primary-700': true,
|
|
||||||
'opacity-50 pointer-events-none': isSubmitting,
|
|
||||||
})}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
<Icon src={require('@tabler/icons/plus.svg')} className='h-5 w-5 text-white' />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<input type='file' className='hidden' ref={fileInput} onChange={handleFileChange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Stack justifyContent='center' space={2}>
|
|
||||||
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
|
||||||
{isSubmitting ? (
|
|
||||||
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isDisabled && (
|
|
||||||
<Button block theme='tertiary' type='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
|
||||||
</Card>
|
<Stack justifyContent='center' space={2}>
|
||||||
|
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isDisabled && (
|
||||||
|
<Button block theme='tertiary' type='button' onClick={onNext}>
|
||||||
|
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</BigCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
|
import { Button, FormGroup, Stack, Textarea } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
@ -43,62 +44,49 @@ const BioStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='xl'>
|
<BigCard
|
||||||
<CardBody>
|
title={<FormattedMessage id='onboarding.note.title' defaultMessage='Write a short bio' />}
|
||||||
|
subtitle={<FormattedMessage id='onboarding.note.subtitle' defaultMessage='You can always edit this later.' />}
|
||||||
|
>
|
||||||
|
<Stack space={5}>
|
||||||
<div>
|
<div>
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
<FormGroup
|
||||||
<Stack space={2}>
|
hintText={<FormattedMessage id='onboarding.bio.hint' defaultMessage='Max 500 characters' />}
|
||||||
<Text size='2xl' align='center' weight='bold'>
|
labelText={<FormattedMessage id='edit_profile.fields.bio_label' defaultMessage='Bio' />}
|
||||||
<FormattedMessage id='onboarding.note.title' defaultMessage='Write a short bio' />
|
errors={errors}
|
||||||
</Text>
|
>
|
||||||
|
<Textarea
|
||||||
|
onChange={(event) => setValue(event.target.value)}
|
||||||
|
placeholder={intl.formatMessage(messages.bioPlaceholder)}
|
||||||
|
value={value}
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Text theme='muted' align='center'>
|
<div>
|
||||||
<FormattedMessage id='onboarding.note.subtitle' defaultMessage='You can always edit this later.' />
|
<Stack justifyContent='center' space={2}>
|
||||||
</Text>
|
<Button
|
||||||
</Stack>
|
block
|
||||||
</div>
|
theme='primary'
|
||||||
|
type='submit'
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Stack space={5}>
|
<Button block theme='tertiary' type='button' onClick={onNext}>
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10'>
|
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
||||||
<FormGroup
|
</Button>
|
||||||
hintText={<FormattedMessage id='onboarding.bio.hint' defaultMessage='Max 500 characters' />}
|
|
||||||
labelText={<FormattedMessage id='edit_profile.fields.bio_label' defaultMessage='Bio' />}
|
|
||||||
errors={errors}
|
|
||||||
>
|
|
||||||
<Textarea
|
|
||||||
onChange={(event) => setValue(event.target.value)}
|
|
||||||
placeholder={intl.formatMessage(messages.bioPlaceholder)}
|
|
||||||
value={value}
|
|
||||||
maxLength={500}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 md:w-1/2'>
|
|
||||||
<Stack justifyContent='center' space={2}>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
theme='primary'
|
|
||||||
type='submit'
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
{isSubmitting ? (
|
|
||||||
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button block theme='tertiary' type='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</Stack>
|
||||||
</Card>
|
</BigCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import StillImage from 'soapbox/components/still-image';
|
import StillImage from 'soapbox/components/still-image';
|
||||||
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
import { Avatar, Button, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
import { isDefaultHeader } from 'soapbox/utils/accounts';
|
import { isDefaultHeader } from 'soapbox/utils/accounts';
|
||||||
|
@ -67,89 +68,74 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='xl'>
|
<BigCard
|
||||||
<CardBody>
|
title={<FormattedMessage id='onboarding.header.title' defaultMessage='Pick a cover image' />}
|
||||||
<div>
|
subtitle={<FormattedMessage id='onboarding.header.subtitle' defaultMessage='This will be shown at the top of your profile.' />}
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
>
|
||||||
<Stack space={2}>
|
<Stack space={10}>
|
||||||
<Text size='2xl' align='center' weight='bold'>
|
<div className='rounded-lg border border-solid border-gray-200 dark:border-gray-800'>
|
||||||
<FormattedMessage id='onboarding.header.title' defaultMessage='Pick a cover image' />
|
<div
|
||||||
</Text>
|
role='button'
|
||||||
|
className='relative flex h-24 items-center justify-center rounded-t-md bg-gray-200 dark:bg-gray-800'
|
||||||
|
>
|
||||||
|
{selectedFile || account?.header && (
|
||||||
|
<StillImage
|
||||||
|
src={selectedFile || account.header}
|
||||||
|
alt={intl.formatMessage(messages.header)}
|
||||||
|
className='absolute inset-0 rounded-t-md object-cover'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text theme='muted' align='center'>
|
{isSubmitting && (
|
||||||
<FormattedMessage id='onboarding.header.subtitle' defaultMessage='This will be shown at the top of your profile.' />
|
<div
|
||||||
</Text>
|
className='absolute inset-0 flex items-center justify-center rounded-t-md bg-white/80 dark:bg-primary-900/80'
|
||||||
</Stack>
|
>
|
||||||
|
<Spinner withText={false} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={openFilePicker}
|
||||||
|
type='button'
|
||||||
|
className={clsx({
|
||||||
|
'absolute -top-3 -right-3 p-1 bg-primary-600 rounded-full ring-2 ring-white dark:ring-primary-900 hover:bg-primary-700': true,
|
||||||
|
'opacity-50 pointer-events-none': isSubmitting,
|
||||||
|
})}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
<Icon src={require('@tabler/icons/plus.svg')} className='h-5 w-5 text-white' />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<input type='file' className='hidden' ref={fileInput} onChange={handleFileChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<div className='flex flex-col px-4 pb-4'>
|
||||||
<Stack space={10}>
|
{account && (
|
||||||
<div className='rounded-lg border border-solid border-gray-200 dark:border-gray-800'>
|
<Avatar src={account.avatar} size={64} className='-mt-8 mb-2 ring-2 ring-white dark:ring-primary-800' />
|
||||||
<div
|
)}
|
||||||
role='button'
|
|
||||||
className='relative flex h-24 items-center justify-center rounded-t-md bg-gray-200 dark:bg-gray-800'
|
|
||||||
>
|
|
||||||
{selectedFile || account?.header && (
|
|
||||||
<StillImage
|
|
||||||
src={selectedFile || account.header}
|
|
||||||
alt={intl.formatMessage(messages.header)}
|
|
||||||
className='absolute inset-0 rounded-t-md object-cover'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isSubmitting && (
|
<Text weight='bold' size='sm'>{account?.display_name}</Text>
|
||||||
<div
|
<Text theme='muted' size='sm'>@{account?.username}</Text>
|
||||||
className='absolute inset-0 flex items-center justify-center rounded-t-md bg-white/80 dark:bg-primary-900/80'
|
|
||||||
>
|
|
||||||
<Spinner withText={false} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={openFilePicker}
|
|
||||||
type='button'
|
|
||||||
className={clsx({
|
|
||||||
'absolute -top-3 -right-3 p-1 bg-primary-600 rounded-full ring-2 ring-white dark:ring-primary-900 hover:bg-primary-700': true,
|
|
||||||
'opacity-50 pointer-events-none': isSubmitting,
|
|
||||||
})}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
<Icon src={require('@tabler/icons/plus.svg')} className='h-5 w-5 text-white' />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<input type='file' className='hidden' ref={fileInput} onChange={handleFileChange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex flex-col px-4 pb-4'>
|
|
||||||
{account && (
|
|
||||||
<Avatar src={account.avatar} size={64} className='-mt-8 mb-2 ring-2 ring-white dark:ring-primary-800' />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Text weight='bold' size='sm'>{account?.display_name}</Text>
|
|
||||||
<Text theme='muted' size='sm'>@{account?.username}</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Stack justifyContent='center' space={2}>
|
|
||||||
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
|
||||||
{isSubmitting ? (
|
|
||||||
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isDisabled && (
|
|
||||||
<Button block theme='tertiary' type='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
|
||||||
</Card>
|
<Stack justifyContent='center' space={2}>
|
||||||
|
<Button block theme='primary' type='button' onClick={onNext} disabled={isDefault && isDisabled || isSubmitting}>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isDisabled && (
|
||||||
|
<Button block theme='tertiary' type='button' onClick={onNext}>
|
||||||
|
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</BigCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
|
import { Button, FormGroup, Input, Stack } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
@ -54,61 +55,46 @@ const DisplayNameStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='xl'>
|
<BigCard
|
||||||
<CardBody>
|
title={<FormattedMessage id='onboarding.display_name.title' defaultMessage='Choose a display name' />}
|
||||||
<div>
|
subtitle={<FormattedMessage id='onboarding.display_name.subtitle' defaultMessage='You can always edit this later.' />}
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
>
|
||||||
<Stack space={2}>
|
<Stack space={5}>
|
||||||
<Text size='2xl' align='center' weight='bold'>
|
<FormGroup
|
||||||
<FormattedMessage id='onboarding.display_name.title' defaultMessage='Choose a display name' />
|
hintText={hintText}
|
||||||
</Text>
|
labelText={<FormattedMessage id='onboarding.display_name.label' defaultMessage='Display name' />}
|
||||||
|
errors={errors}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
onChange={(event) => setValue(event.target.value)}
|
||||||
|
placeholder={intl.formatMessage(messages.usernamePlaceholder)}
|
||||||
|
type='text'
|
||||||
|
value={value}
|
||||||
|
maxLength={30}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<Text theme='muted' align='center'>
|
<Stack justifyContent='center' space={2}>
|
||||||
<FormattedMessage id='onboarding.display_name.subtitle' defaultMessage='You can always edit this later.' />
|
<Button
|
||||||
</Text>
|
block
|
||||||
</Stack>
|
theme='primary'
|
||||||
</div>
|
type='submit'
|
||||||
|
disabled={isDisabled || isSubmitting}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className='mx-auto sm:w-2/3 sm:pt-10 md:w-1/2'>
|
<Button block theme='tertiary' type='button' onClick={onNext}>
|
||||||
<Stack space={5}>
|
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
||||||
<FormGroup
|
</Button>
|
||||||
hintText={hintText}
|
</Stack>
|
||||||
labelText={<FormattedMessage id='onboarding.display_name.label' defaultMessage='Display name' />}
|
</Stack>
|
||||||
errors={errors}
|
</BigCard>
|
||||||
>
|
|
||||||
<Input
|
|
||||||
onChange={(event) => setValue(event.target.value)}
|
|
||||||
placeholder={intl.formatMessage(messages.usernamePlaceholder)}
|
|
||||||
type='text'
|
|
||||||
value={value}
|
|
||||||
maxLength={30}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<Stack justifyContent='center' space={2}>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
theme='primary'
|
|
||||||
type='submit'
|
|
||||||
disabled={isDisabled || isSubmitting}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
{isSubmitting ? (
|
|
||||||
<FormattedMessage id='onboarding.saving' defaultMessage='Saving…' />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage id='onboarding.next' defaultMessage='Next' />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button block theme='tertiary' type='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ import debounce from 'lodash/debounce';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||||
import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui';
|
import { Button, Stack, Text } from 'soapbox/components/ui';
|
||||||
import AccountContainer from 'soapbox/containers/account-container';
|
import AccountContainer from 'soapbox/containers/account-container';
|
||||||
import { useOnboardingSuggestions } from 'soapbox/queries/suggestions';
|
import { useOnboardingSuggestions } from 'soapbox/queries/suggestions';
|
||||||
|
|
||||||
|
@ -66,43 +67,28 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant='rounded' size='xl'>
|
<BigCard
|
||||||
<CardBody>
|
title={<FormattedMessage id='onboarding.suggestions.title' defaultMessage='Suggested accounts' />}
|
||||||
<div>
|
subtitle={<FormattedMessage id='onboarding.suggestions.subtitle' defaultMessage='Here are a few of the most popular accounts you might like.' />}
|
||||||
<div className='-mx-4 mb-4 border-b border-solid border-gray-200 pb-4 dark:border-gray-800 sm:-mx-10 sm:pb-10'>
|
>
|
||||||
<Stack space={2}>
|
{renderBody()}
|
||||||
<Text size='2xl' align='center' weight='bold'>
|
|
||||||
<FormattedMessage id='onboarding.suggestions.title' defaultMessage='Suggested accounts' />
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text theme='muted' align='center'>
|
<Stack>
|
||||||
<FormattedMessage id='onboarding.suggestions.subtitle' defaultMessage='Here are a few of the most popular accounts you might like.' />
|
<Stack justifyContent='center' space={2}>
|
||||||
</Text>
|
<Button
|
||||||
</Stack>
|
block
|
||||||
</div>
|
theme='primary'
|
||||||
|
onClick={onNext}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='onboarding.done' defaultMessage='Done' />
|
||||||
|
</Button>
|
||||||
|
|
||||||
{renderBody()}
|
<Button block theme='tertiary' type='button' onClick={onNext}>
|
||||||
|
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
||||||
<div className='mx-auto sm:w-2/3 md:w-1/2'>
|
</Button>
|
||||||
<Stack>
|
</Stack>
|
||||||
<Stack justifyContent='center' space={2}>
|
</Stack>
|
||||||
<Button
|
</BigCard>
|
||||||
block
|
|
||||||
theme='primary'
|
|
||||||
onClick={onNext}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='onboarding.done' defaultMessage='Done' />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button block theme='tertiary' type='button' onClick={onNext}>
|
|
||||||
<FormattedMessage id='onboarding.skip' defaultMessage='Skip for now' />
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Stack, CardTitle, Text } from 'soapbox/components/ui';
|
import { BigCard } from 'soapbox/components/big-card';
|
||||||
import RegistrationForm from 'soapbox/features/auth-login/components/registration-form';
|
import RegistrationForm from 'soapbox/features/auth-login/components/registration-form';
|
||||||
import { useInstance } from 'soapbox/hooks';
|
import { useInstance } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -23,21 +23,17 @@ const RegisterInvite: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const subtitle = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='register_invite.lead'
|
||||||
|
defaultMessage='Complete the form below to create an account.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space={3}>
|
<BigCard title={title} subtitle={subtitle}>
|
||||||
<Stack className='mb-4'>
|
|
||||||
<CardTitle title={title} />
|
|
||||||
|
|
||||||
<Text theme='muted'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='register_invite.lead'
|
|
||||||
defaultMessage='Complete the form below to create an account.'
|
|
||||||
/>
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<RegistrationForm inviteToken={token} />
|
<RegistrationForm inviteToken={token} />
|
||||||
</Stack>
|
</BigCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,12 @@ import { Stack } from 'soapbox/components/ui';
|
||||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
|
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
|
||||||
import { HotKeys } from 'soapbox/features/ui/components/hotkeys';
|
import { HotKeys } from 'soapbox/features/ui/components/hotkeys';
|
||||||
import PendingStatus from 'soapbox/features/ui/components/pending-status';
|
import PendingStatus from 'soapbox/features/ui/components/pending-status';
|
||||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
import { RootState } from 'soapbox/store';
|
import { RootState } from 'soapbox/store';
|
||||||
import { type Account, type Status } from 'soapbox/types/entities';
|
import { type Account, type Status } from 'soapbox/types/entities';
|
||||||
import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/status';
|
import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/status';
|
||||||
|
|
||||||
import DetailedStatus from './detailed-status';
|
import DetailedStatus from './detailed-status';
|
||||||
import ThreadLoginCta from './thread-login-cta';
|
|
||||||
import ThreadStatus from './thread-status';
|
import ThreadStatus from './thread-status';
|
||||||
|
|
||||||
type DisplayMedia = 'default' | 'hide_all' | 'show_all';
|
type DisplayMedia = 'default' | 'hide_all' | 'show_all';
|
||||||
|
@ -97,7 +96,6 @@ const Thread = (props: IThread) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { account: me } = useOwnAccount();
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
||||||
|
@ -459,8 +457,6 @@ const Thread = (props: IThread) => {
|
||||||
{children}
|
{children}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!me && <ThreadLoginCta />}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,12 +9,13 @@ import {
|
||||||
} from 'soapbox/actions/statuses';
|
} from 'soapbox/actions/statuses';
|
||||||
import MissingIndicator from 'soapbox/components/missing-indicator';
|
import MissingIndicator from 'soapbox/components/missing-indicator';
|
||||||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||||
import { Column } from 'soapbox/components/ui';
|
import { Column, Stack } from 'soapbox/components/ui';
|
||||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
|
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
|
||||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useLoggedIn } from 'soapbox/hooks';
|
||||||
import { makeGetStatus } from 'soapbox/selectors';
|
import { makeGetStatus } from 'soapbox/selectors';
|
||||||
|
|
||||||
import Thread from './components/thread';
|
import Thread from './components/thread';
|
||||||
|
import ThreadLoginCta from './components/thread-login-cta';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'status.title', defaultMessage: 'Post Details' },
|
title: { id: 'status.title', defaultMessage: 'Post Details' },
|
||||||
|
@ -47,6 +48,7 @@ interface IStatusDetails {
|
||||||
const StatusDetails: React.FC<IStatusDetails> = (props) => {
|
const StatusDetails: React.FC<IStatusDetails> = (props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { isLoggedIn } = useLoggedIn();
|
||||||
|
|
||||||
const getStatus = useCallback(makeGetStatus(), []);
|
const getStatus = useCallback(makeGetStatus(), []);
|
||||||
const status = useAppSelector((state) => getStatus(state, { id: props.params.statusId }));
|
const status = useAppSelector((state) => getStatus(state, { id: props.params.statusId }));
|
||||||
|
@ -113,15 +115,19 @@ const StatusDetails: React.FC<IStatusDetails> = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(titleMessage())}>
|
<Stack space={4}>
|
||||||
<PullToRefresh onRefresh={handleRefresh}>
|
<Column label={intl.formatMessage(titleMessage())}>
|
||||||
<Thread
|
<PullToRefresh onRefresh={handleRefresh}>
|
||||||
status={status}
|
<Thread
|
||||||
next={next}
|
status={status}
|
||||||
handleLoadMore={handleLoadMore}
|
next={next}
|
||||||
/>
|
handleLoadMore={handleLoadMore}
|
||||||
</PullToRefresh>
|
/>
|
||||||
</Column>
|
</PullToRefresh>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
{!isLoggedIn && <ThreadLoginCta />}
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,11 @@ import {
|
||||||
FollowedTags,
|
FollowedTags,
|
||||||
AboutPage,
|
AboutPage,
|
||||||
RegistrationPage,
|
RegistrationPage,
|
||||||
|
LoginPage,
|
||||||
|
PasswordReset,
|
||||||
|
PasswordResetConfirm,
|
||||||
|
RegisterInvite,
|
||||||
|
ExternalLogin,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import GlobalHotkeys from './util/global-hotkeys';
|
import GlobalHotkeys from './util/global-hotkeys';
|
||||||
import { WrappedRoute } from './util/react-router-helpers';
|
import { WrappedRoute } from './util/react-router-helpers';
|
||||||
|
@ -357,6 +362,15 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
<WrappedRoute path='/signup' page={DefaultPage} component={RegistrationPage} publicRoute exact />
|
<WrappedRoute path='/signup' page={DefaultPage} component={RegistrationPage} publicRoute exact />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<WrappedRoute path='/login/external' page={DefaultPage} component={ExternalLogin} publicRoute exact />
|
||||||
|
<WrappedRoute path='/login/add' page={DefaultPage} component={LoginPage} publicRoute exact />
|
||||||
|
<WrappedRoute path='/login' page={DefaultPage} component={LoginPage} publicRoute exact />
|
||||||
|
<WrappedRoute path='/reset-password' page={DefaultPage} component={PasswordReset} publicRoute exact />
|
||||||
|
<WrappedRoute path='/edit-password' page={DefaultPage} component={PasswordResetConfirm} publicRoute exact />
|
||||||
|
<WrappedRoute path='/invite/:token' page={DefaultPage} component={RegisterInvite} publicRoute exact />
|
||||||
|
<Redirect from='/auth/password/new' to='/reset-password' />
|
||||||
|
<Redirect from='/auth/password/edit' to={`/edit-password${search}`} />
|
||||||
|
|
||||||
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
|
<WrappedRoute page={EmptyPage} component={GenericNotFound} content={children} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|
Ładowanie…
Reference in New Issue