diff --git a/app/soapbox/components/ui/button/useButtonStyles.ts b/app/soapbox/components/ui/button/useButtonStyles.ts index 1b52e9a05..e064c9e23 100644 --- a/app/soapbox/components/ui/button/useButtonStyles.ts +++ b/app/soapbox/components/ui/button/useButtonStyles.ts @@ -19,7 +19,7 @@ const useButtonStyles = ({ }: IButtonStyles) => { const themes = { 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: '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', diff --git a/app/soapbox/components/ui/tooltip/tooltip.css b/app/soapbox/components/ui/tooltip/tooltip.css index 3140307dc..6d439ddb3 100644 --- a/app/soapbox/components/ui/tooltip/tooltip.css +++ b/app/soapbox/components/ui/tooltip/tooltip.css @@ -4,5 +4,5 @@ [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; - z-index: 1; + z-index: 100; } diff --git a/app/soapbox/features/ui/components/navbar.tsx b/app/soapbox/features/ui/components/navbar.tsx index 4b2b69889..ac81681d9 100644 --- a/app/soapbox/features/ui/components/navbar.tsx +++ b/app/soapbox/features/ui/components/navbar.tsx @@ -1,11 +1,14 @@ +import { AxiosError } from 'axios'; import classNames from 'classnames'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; +import React, { useRef, useState } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; 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 { 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 { useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; @@ -13,16 +16,56 @@ import { openSidebar } from '../../../actions/sidebar'; 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 dispatch = useDispatch(); - const node = React.useRef(null); + const intl = useIntl(); + + const node = useRef(null); const account = useOwnAccount(); const soapboxConfig = useSoapboxConfig(); const singleUserMode = soapboxConfig.get('singleUserMode'); + const [isLoading, setLoading] = useState(false); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [mfaToken, setMfaToken] = useState(false); + 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 ; + return (