Add login form to navbar
This commit is contained in:
parent
938665f157
commit
de1d630daa
4 changed files with 100 additions and 17 deletions
|
@ -19,7 +19,7 @@ const useButtonStyles = ({
|
||||||
}: IButtonStyles) => {
|
}: IButtonStyles) => {
|
||||||
const themes = {
|
const themes = {
|
||||||
primary:
|
primary:
|
||||||
'border-transparent text-white bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
'border-primary-600 text-white bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
||||||
secondary:
|
secondary:
|
||||||
'border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
'border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
||||||
ghost: 'shadow-none border-gray-200 text-gray-700 bg-white dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
ghost: 'shadow-none border-gray-200 text-gray-700 bg-white dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2',
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
|
|
||||||
[data-reach-tooltip] {
|
[data-reach-tooltip] {
|
||||||
@apply pointer-events-none absolute px-2.5 py-1.5 rounded shadow whitespace-nowrap text-xs font-medium bg-gray-800 text-white;
|
@apply pointer-events-none absolute px-2.5 py-1.5 rounded shadow whitespace-nowrap text-xs font-medium bg-gray-800 text-white;
|
||||||
z-index: 1;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as React from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||||
|
import { fetchInstance } from 'soapbox/actions/instance';
|
||||||
import SiteLogo from 'soapbox/components/site-logo';
|
import SiteLogo from 'soapbox/components/site-logo';
|
||||||
import { Avatar, Button } from 'soapbox/components/ui';
|
import { Avatar, Button, Form, IconButton, Input, Tooltip } from 'soapbox/components/ui';
|
||||||
import Search from 'soapbox/features/compose/components/search';
|
import Search from 'soapbox/features/compose/components/search';
|
||||||
import { useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
|
import { useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -13,16 +16,56 @@ import { openSidebar } from '../../../actions/sidebar';
|
||||||
|
|
||||||
import ProfileDropdown from './profile-dropdown';
|
import ProfileDropdown from './profile-dropdown';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
login: { id: 'navbar.login.action', defaultMessage: 'Log in' },
|
||||||
|
username: { id: 'navbar.login.username.placeholder', defaultMessage: 'Email or username' },
|
||||||
|
password: { id: 'navbar.login.password.label', defaultMessage: 'Password' },
|
||||||
|
forgotPassword: { id: 'navbar.login.forgot_password', defaultMessage: 'Forgot password?' },
|
||||||
|
});
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const node = React.useRef(null);
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const node = useRef(null);
|
||||||
|
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
const singleUserMode = soapboxConfig.get('singleUserMode');
|
const singleUserMode = soapboxConfig.get('singleUserMode');
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
|
const [username, setUsername] = useState<string>('');
|
||||||
|
const [password, setPassword] = useState<string>('');
|
||||||
|
const [mfaToken, setMfaToken] = useState<boolean>(false);
|
||||||
|
|
||||||
const onOpenSidebar = () => dispatch(openSidebar());
|
const onOpenSidebar = () => dispatch(openSidebar());
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
dispatch(logIn(intl, username, password) as any)
|
||||||
|
.then(({ access_token }: { access_token: string }) => {
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
dispatch(verifyCredentials(access_token) as any)
|
||||||
|
// Refetch the instance for authenticated fetch
|
||||||
|
.then(() => dispatch(fetchInstance()))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((error: AxiosError) => {
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
const data: any = error.response?.data;
|
||||||
|
if (data?.error === 'mfa_required') {
|
||||||
|
setMfaToken(data.mfa_token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='bg-white dark:bg-slate-800 shadow z-50 sticky top-0' ref={node}>
|
<nav className='bg-white dark:bg-slate-800 shadow z-50 sticky top-0' ref={node}>
|
||||||
<div className='max-w-7xl mx-auto px-2 sm:px-6 lg:px-8'>
|
<div className='max-w-7xl mx-auto px-2 sm:px-6 lg:px-8'>
|
||||||
|
@ -64,17 +107,57 @@ const Navbar = () => {
|
||||||
</ProfileDropdown>
|
</ProfileDropdown>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='space-x-1.5'>
|
<>
|
||||||
<Button theme='secondary' to='/login' size='sm'>
|
<Form className='hidden lg:flex space-x-2 items-center' onSubmit={handleSubmit}>
|
||||||
<FormattedMessage id='account.login' defaultMessage='Log In' />
|
<Input
|
||||||
</Button>
|
required
|
||||||
|
value={username}
|
||||||
|
onChange={(event) => setUsername(event.target.value)}
|
||||||
|
type='text'
|
||||||
|
placeholder={intl.formatMessage(messages.username)}
|
||||||
|
className='max-w-[200px]'
|
||||||
|
/>
|
||||||
|
|
||||||
{!singleUserMode && (
|
<Input
|
||||||
<Button theme='primary' to='/signup' size='sm'>
|
required
|
||||||
<FormattedMessage id='account.register' defaultMessage='Sign up' />
|
value={password}
|
||||||
|
onChange={(event) => setPassword(event.target.value)}
|
||||||
|
type='password'
|
||||||
|
placeholder={intl.formatMessage(messages.password)}
|
||||||
|
className='max-w-[200px]'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link to='/reset-password'>
|
||||||
|
<Tooltip text={intl.formatMessage(messages.forgotPassword)}>
|
||||||
|
<IconButton
|
||||||
|
src={require('@tabler/icons/icons/help.svg')}
|
||||||
|
className='bg-transparent text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer'
|
||||||
|
iconClassName='w-5 h-5'
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
theme='primary'
|
||||||
|
type='submit'
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.login)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</Form>
|
||||||
</div>
|
|
||||||
|
<div className='space-x-1.5 lg:hidden'>
|
||||||
|
<Button theme='secondary' to='/login' size='sm'>
|
||||||
|
<FormattedMessage id='account.login' defaultMessage='Log In' />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{!singleUserMode && (
|
||||||
|
<Button theme='primary' to='/signup' size='sm'>
|
||||||
|
<FormattedMessage id='account.register' defaultMessage='Sign up' />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { dismissAlert } from '../../../actions/alerts';
|
||||||
import { getAlerts } from '../../../selectors';
|
import { getAlerts } from '../../../selectors';
|
||||||
|
|
||||||
const CustomNotificationStack = (props) => (
|
const CustomNotificationStack = (props) => (
|
||||||
<div role='assertive' data-testid='toast' className='z-1000 fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start'>
|
<div role='assertive' data-testid='toast' className='z-1000 fixed inset-0 flex items-end px-4 py-6 pointer-events-none pt-16 lg:pt-20 sm:items-start'>
|
||||||
<NotificationStack {...props} />
|
<NotificationStack {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue