diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index a21d0596d5..7cddb854ef 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -11,7 +11,7 @@ import { basename, createHook } from './core/utils'; import configureStore from './core/store/configureStore'; import { Plugin } from './core/apis'; import App from './pages/App'; -import AuthLogo from './assets/images/logo_strapi_auth.png'; +import AuthLogo from './assets/images/logo_strapi_auth_v4.png'; import MenuLogo from './assets/images/strapi-img.png'; import Providers from './components/Providers'; import Theme from './components/Theme'; diff --git a/packages/core/admin/admin/src/assets/images/logo_strapi_auth_v4.png b/packages/core/admin/admin/src/assets/images/logo_strapi_auth_v4.png new file mode 100644 index 0000000000..ff23ded756 Binary files /dev/null and b/packages/core/admin/admin/src/assets/images/logo_strapi_auth_v4.png differ diff --git a/packages/core/admin/admin/src/components/LeftMenu/index.js b/packages/core/admin/admin/src/components/LeftMenu/index.js index c4a2170a6e..f96797d1ea 100644 --- a/packages/core/admin/admin/src/components/LeftMenu/index.js +++ b/packages/core/admin/admin/src/components/LeftMenu/index.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import get from 'lodash/get'; +import { useHistory } from 'react-router-dom'; import { MainNav, NavBrand, @@ -12,6 +13,7 @@ import { NavUser, NavCondense, Divider, + Button, } from '@strapi/parts'; import ContentIcon from '@strapi/icons/ContentIcon'; import { auth, usePersistentState } from '@strapi/helper-plugin'; @@ -19,6 +21,7 @@ import useConfigurations from '../../hooks/useConfigurations'; const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => { const { menuLogo } = useConfigurations(); + const { push } = useHistory(); const [condensed, setCondensed] = usePersistentState('navbar-condensed', false); const userInfo = auth.getUserInfo(); @@ -63,6 +66,16 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => { ))} + {/* This is temporary */} + ) : null} diff --git a/packages/core/admin/admin/src/components/LocaleToggle/index.js b/packages/core/admin/admin/src/components/LocaleToggle/index.js index 49c4505ac3..23cfe81c7a 100644 --- a/packages/core/admin/admin/src/components/LocaleToggle/index.js +++ b/packages/core/admin/admin/src/components/LocaleToggle/index.js @@ -4,42 +4,22 @@ * */ -import React, { useState } from 'react'; +import React from 'react'; import { useIntl } from 'react-intl'; -import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; +import { SimpleMenu, MenuItem } from '@strapi/parts'; import useLocalesProvider from '../LocalesProvider/useLocalesProvider'; -import Wrapper from './Wrapper'; const LocaleToggle = () => { const { changeLocale, localeNames } = useLocalesProvider(); - - const [isOpen, setIsOpen] = useState(false); - const toggle = () => setIsOpen(prev => !prev); const { locale } = useIntl(); return ( - - - - {localeNames[locale]} - - - - {Object.keys(localeNames).map(lang => { - return ( - changeLocale(lang)} - className={`localeToggleItem ${locale === lang ? 'localeToggleItemActive' : ''}`} - > - {localeNames[lang]} - - ); - })} - - - + + {Object.keys(localeNames).map(lang => ( + changeLocale(lang)} key={lang}>{localeNames[lang]} + ))} + ); }; -export default LocaleToggle; +export default LocaleToggle; \ No newline at end of file diff --git a/packages/core/admin/admin/src/components/LocaleToggle/tests/index.test.js b/packages/core/admin/admin/src/components/LocaleToggle/tests/index.test.js deleted file mode 100644 index 1fe55951ca..0000000000 --- a/packages/core/admin/admin/src/components/LocaleToggle/tests/index.test.js +++ /dev/null @@ -1,217 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import LanguageProvider from '../../LanguageProvider'; -import en from '../../../translations/en.json'; -import LocaleToggle from '../index'; - -const messages = { en }; -const localeNames = { en: 'English' }; - -describe('', () => { - it('should not crash', () => { - const App = ( - - - - ); - - const { container } = render(App); - expect(container.firstChild).toMatchInlineSnapshot(` - .c0 { - -webkit-font-smoothing: antialiased; - } - - .c0 > div { - height: 6rem; - line-height: 5.8rem; - z-index: 999; - } - - .c0 > div > button { - width: 100%; - padding: 0 30px; - background: transparent; - border: none; - border-radius: 0; - color: #333740; - font-weight: 500; - text-align: right; - cursor: pointer; - -webkit-transition: background 0.2s ease-out; - transition: background 0.2s ease-out; - } - - .c0 > div > button:hover, - .c0 > div > button:focus, - .c0 > div > button:active { - color: #333740; - background-color: #fafafb !important; - } - - .c0 > div > button > i, - .c0 > div > button > svg { - margin-left: 10px; - -webkit-transition: -webkit-transform 0.3s ease-out; - -webkit-transition: transform 0.3s ease-out; - transition: transform 0.3s ease-out; - } - - .c0 > div > button > i[alt='true'], - .c0 > div > button > svg[alt='true'] { - -webkit-transform: rotateX(180deg); - -ms-transform: rotateX(180deg); - transform: rotateX(180deg); - } - - .c0 .localeDropdownContent { - -webkit-font-smoothing: antialiased; - } - - .c0 .localeDropdownContent span { - color: #333740; - font-size: 13px; - font-family: Lato; - font-weight: 500; - -webkit-letter-spacing: 0.5; - -moz-letter-spacing: 0.5; - -ms-letter-spacing: 0.5; - letter-spacing: 0.5; - vertical-align: baseline; - } - - .c0 .localeDropdownMenu { - min-width: 90px !important; - max-height: 162px !important; - overflow: auto !important; - margin: 0 !important; - padding: 0; - line-height: 1.8rem; - border: none !important; - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - box-shadow: 0 1px 4px 0px rgba(40,42,49,0.05); - } - - .c0 .localeDropdownMenu:before { - content: ''; - position: absolute; - top: -3px; - left: -1px; - width: calc(100% + 1px); - height: 3px; - box-shadow: 0 1px 2px 0 rgba(40,42,49,0.16); - } - - .c0 .localeDropdownMenu > button { - height: 40px; - padding: 0px 15px; - line-height: 40px; - color: #f75b1d; - font-size: 13px; - font-weight: 500; - -webkit-letter-spacing: 0.5; - -moz-letter-spacing: 0.5; - -ms-letter-spacing: 0.5; - letter-spacing: 0.5; - } - - .c0 .localeDropdownMenu > button:hover, - .c0 .localeDropdownMenu > button:focus, - .c0 .localeDropdownMenu > button:active { - background-color: #fafafb !important; - border-radius: 0px; - cursor: pointer; - } - - .c0 .localeDropdownMenu > button:first-child { - line-height: 50px; - margin-bottom: 4px; - } - - .c0 .localeDropdownMenu > button:first-child:hover, - .c0 .localeDropdownMenu > button:first-child:active { - color: #333740; - } - - .c0 .localeDropdownMenu > button:not(:first-child) { - height: 36px; - line-height: 36px; - } - - .c0 .localeDropdownMenu > button:not(:first-child) > i, - .c0 .localeDropdownMenu > button:not(:first-child) > svg { - margin-left: 10px; - } - - .c0 .localeDropdownMenuNotLogged { - background: transparent !important; - box-shadow: none !important; - border: 1px solid #e3e9f3 !important; - border-top: 0px !important; - } - - .c0 .localeDropdownMenuNotLogged button { - padding-left: 17px; - } - - .c0 .localeDropdownMenuNotLogged button:hover { - background-color: #f7f8f8 !important; - } - - .c0 .localeDropdownMenuNotLogged:before { - box-shadow: none !important; - } - - .c0 .localeToggleItem img { - max-height: 13.37px; - margin-left: 9px; - } - - .c0 .localeToggleItem:active { - color: black; - } - - .c0 .localeToggleItem:hover { - background-color: #fafafb !important; - } - - .c0 .localeToggleItemActive { - color: #333740 !important; - } - -
-
- - -
-
- `); - }); -}); diff --git a/packages/core/admin/admin/src/layouts/UnauthenticatedLayout.js b/packages/core/admin/admin/src/layouts/UnauthenticatedLayout.js new file mode 100644 index 0000000000..6d99c91460 --- /dev/null +++ b/packages/core/admin/admin/src/layouts/UnauthenticatedLayout.js @@ -0,0 +1,43 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { Box, Row } from '@strapi/parts'; +import LocaleToggle from '../components/LocaleToggle'; + +const Wrapper = styled(Box)` + margin: 0 auto; + width: 552px; +`; + +export const Column = styled(Row)` + flex-direction: column; +`; + +const UnauthenticatedLayout = ({ children }) => { + return ( +
+ + + + + + + + {children} + + +
+ ); +}; + +UnauthenticatedLayout.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default UnauthenticatedLayout; diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js index d1216617c5..1ffa600906 100644 --- a/packages/core/admin/admin/src/pages/App/index.js +++ b/packages/core/admin/admin/src/pages/App/index.js @@ -13,6 +13,8 @@ import { useNotification, TrackingContext, } from '@strapi/helper-plugin'; +import { SkipToContent } from '@strapi/parts'; +import { useIntl } from 'react-intl'; import PrivateRoute from '../../components/PrivateRoute'; import { createRoute, makeUniqueRoutes } from '../../utils'; import AuthPage from '../AuthPage'; @@ -26,6 +28,7 @@ const AuthenticatedApp = lazy(() => function App() { const toggleNotification = useNotification(); + const { formatMessage } = useIntl(); const [{ isLoading, hasAdmin, uuid }, setState] = useState({ isLoading: true, hasAdmin: false }); const authRoutes = useMemo(() => { @@ -105,6 +108,7 @@ function App() { return ( }> + {formatMessage({ id: 'skipToContent' })} {authRoutes} diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Login/BaseLogin.js b/packages/core/admin/admin/src/pages/AuthPage/components/Login/BaseLogin.js index cf45d1cf1f..6e65046efb 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Login/BaseLogin.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Login/BaseLogin.js @@ -1,84 +1,139 @@ -import React from 'react'; -import { Checkbox } from '@buffetjs/core'; -import { useIntl } from 'react-intl'; -import { get } from 'lodash'; +import React, { useState } from 'react'; +import { Show, Hide } from '@strapi/icons'; +import { + Box, + Stack, + H1, + Text, + Subtitle, + Button, + Checkbox, + TextInput, + Main, + FieldAction, +} from '@strapi/parts'; import PropTypes from 'prop-types'; -import { BaselineAlignment } from '@strapi/helper-plugin'; +import styled from 'styled-components'; +import { useIntl } from 'react-intl'; +import { Formik } from 'formik'; -import Button from '../../../../components/FullWidthButton'; -import AuthLink from '../AuthLink'; -import Box from '../Box'; -import Input from '../Input'; +import { Column } from '../../../../layouts/UnauthenticatedLayout'; +import Form from './Form'; import Logo from '../Logo'; -import Section from '../Section'; -const Login = ({ children, formErrors, modifiedData, onChange, onSubmit, requestError }) => { +const AuthButton = styled(Button)` + display: inline-block; + width: 100%; +`; +const FieldActionWrapper = styled(FieldAction)` + svg { + height: 16px; + width: 16px; + path { + fill: ${({ theme }) => theme.colors.neutral600}; + } + } +`; + +const Login = ({ onSubmit, schema }) => { + const [passwordShown, setPasswordShown] = useState(false); const { formatMessage } = useIntl(); return ( - <> -
- -
-
- - -
- + + {({ values, errors, handleChange }) => ( + + + + +

{formatMessage({ id: 'Auth.form.welcome.title' })}

+
+ + + {formatMessage({ id: 'Auth.form.welcome.subtitle' })} + + + {errors.errorMessage && ( + + {errors.errorMessage} + + )} +
+ + + - { + e.stopPropagation(); + setPasswordShown(prev => !prev); + }} + label={formatMessage({ + id: passwordShown + ? 'Auth.form.password.show-password' + : 'Auth.form.password.hide-password', + })} + > + {passwordShown ? : } + + } + required /> { + handleChange({ target: { value: checked, name: 'rememberMe' } }); + }} + value={values.rememberMe} name="rememberMe" - onChange={onChange} - value={modifiedData.rememberMe} - /> - - - - - {children} -
-
-
- - + > + {formatMessage({ id: 'Auth.form.rememberMe.label' })} + + + {formatMessage({ id: 'Auth.form.button.login' })} + + + + )} + + ); }; Login.defaultProps = { - children: null, - onSubmit: e => e.preventDefault(), - requestError: null, + onSubmit: () => {}, }; Login.propTypes = { - children: PropTypes.node, - formErrors: PropTypes.object.isRequired, - modifiedData: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, onSubmit: PropTypes.func, - requestError: PropTypes.object, + schema: PropTypes.shape({ + type: PropTypes.string.isRequired, + }).isRequired, }; export default Login; diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Login/Form.js b/packages/core/admin/admin/src/pages/AuthPage/components/Login/Form.js new file mode 100644 index 0000000000..ee273000cb --- /dev/null +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Login/Form.js @@ -0,0 +1,43 @@ +import React, { useEffect } from 'react'; +import { Form, useFormikContext, getIn } from 'formik'; + +const FormWithFocus = props => { + const { isSubmitting, isValidating, errors, touched } = useFormikContext(); + + useEffect(() => { + if (isSubmitting && !isValidating) { + const errorNames = Object.keys(touched).reduce((prev, key) => { + if (getIn(errors, key)) { + prev.push(key); + } + + return prev; + }, []); + + if (errorNames.length) { + let errorEl; + + errorNames.forEach(errorKey => { + const selector = `[name="${errorKey}"]`; + + if (!errorEl) { + errorEl = document.querySelector(selector); + } + }); + + errorEl.focus(); + } + } + if (!isSubmitting && !isValidating && Object.keys(errors).length) { + const el = document.getElementById('global-form-error'); + + if (el) { + el.focus(); + } + } + }, [errors, isSubmitting, isValidating, touched]); + + return
; +}; + +export default FormWithFocus; diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Login/index.js b/packages/core/admin/admin/src/pages/AuthPage/components/Login/index.js index 7ca153d3de..2bfc7100c3 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Login/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Login/index.js @@ -1,10 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; - import BaseLogin from './BaseLogin'; +import UnauthenticatedLayout from '../../../../layouts/UnauthenticatedLayout'; const Login = loginProps => { - return ; + return ( + + + + ); }; Login.defaultProps = { diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/Img.js b/packages/core/admin/admin/src/pages/AuthPage/components/Logo/Img.js deleted file mode 100644 index a12578ffcc..0000000000 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/Img.js +++ /dev/null @@ -1,7 +0,0 @@ -import styled from 'styled-components'; - -const Img = styled.img` - height: 40px; -`; - -export default Img; diff --git a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js b/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js index 359f707cdc..ca2f2edb37 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/components/Logo/index.js @@ -1,11 +1,15 @@ import React from 'react'; +import styled from 'styled-components'; import { useConfigurations } from '../../../../hooks'; -import Img from './Img'; + +const Img = styled.img` + height: 72px; +`; const Logo = () => { const { authLogo } = useConfigurations(); - return strapi; + return ; }; export default Logo; diff --git a/packages/core/admin/admin/src/pages/AuthPage/index.js b/packages/core/admin/admin/src/pages/AuthPage/index.js index b249afcf4e..d368e1d48f 100644 --- a/packages/core/admin/admin/src/pages/AuthPage/index.js +++ b/packages/core/admin/admin/src/pages/AuthPage/index.js @@ -1,16 +1,11 @@ import React, { useEffect, useReducer } from 'react'; import axios from 'axios'; -import { camelCase, get, omit, upperFirst } from 'lodash'; +import { camelCase, get, omit } from 'lodash'; import { Redirect, useRouteMatch, useHistory } from 'react-router-dom'; -import { BaselineAlignment, auth, useNotification, useQuery } from '@strapi/helper-plugin'; -import { Padded } from '@buffetjs/core'; +import { auth, useNotification, useQuery } from '@strapi/helper-plugin'; import PropTypes from 'prop-types'; import forms from 'ee_else_ce/pages/AuthPage/utils/forms'; import useLocalesProvider from '../../components/LocalesProvider/useLocalesProvider'; -import NavTopRightWrapper from '../../components/NavTopRightWrapper'; -import PageTitle from '../../components/PageTitle'; -import LocaleToggle from '../../components/LocaleToggle'; -import checkFormValidity from '../../utils/checkFormValidity'; import formatAPIErrors from '../../utils/formatAPIErrors'; import init from './init'; import { initialState, reducer } from './reducer'; @@ -95,40 +90,25 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { }); }; - const handleSubmit = async e => { - e.preventDefault(); + const handleSubmit = async (e, { setSubmitting, setErrors }) => { + setSubmitting(true); + const body = omit(e, fieldsToOmit); + const requestURL = `/admin/${endPoint}`; - dispatch({ - type: 'SET_ERRORS', - errors: {}, - }); + if (authType === 'login') { + await loginRequest(body, requestURL, { setSubmitting, setErrors }); + } - const errors = await checkFormValidity(modifiedData, schema); + if (authType === 'register' || authType === 'register-admin') { + await registerRequest(body, requestURL); + } - dispatch({ - type: 'SET_ERRORS', - errors: errors || {}, - }); + if (authType === 'forgot-password') { + await forgotPasswordRequest(body, requestURL); + } - if (!errors) { - const body = omit(modifiedData, fieldsToOmit); - const requestURL = `/admin/${endPoint}`; - - if (authType === 'login') { - await loginRequest(body, requestURL); - } - - if (authType === 'register' || authType === 'register-admin') { - await registerRequest(body, requestURL); - } - - if (authType === 'forgot-password') { - await forgotPasswordRequest(body, requestURL); - } - - if (authType === 'reset-password') { - await resetPasswordRequest(body, requestURL); - } + if (authType === 'reset-password') { + await resetPasswordRequest(body, requestURL); } }; @@ -152,7 +132,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { } }; - const loginRequest = async (body, requestURL) => { + const loginRequest = async (body, requestURL, { setSubmitting, setErrors }) => { try { const { data: { @@ -175,8 +155,8 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { push('/'); } catch (err) { if (err.response) { + setSubmitting(false); const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong'); - const errorStatus = get(err, ['response', 'data', 'statusCode'], 400); if (camelCase(errorMessage).toLowerCase() === 'usernotactive') { push('/auth/oops'); @@ -188,11 +168,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { return; } - dispatch({ - type: 'SET_REQUEST_ERROR', - errorMessage, - errorStatus, - }); + setErrors({ errorMessage }); } } }; @@ -288,24 +264,17 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => { } return ( - - - - - - - - - + ); }; diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json index 23ba3901e7..030477126d 100644 --- a/packages/core/admin/admin/src/translations/en.json +++ b/packages/core/admin/admin/src/translations/en.json @@ -3,7 +3,7 @@ "Auth.components.Oops.text": "Your account has been suspended", "Auth.form.button.forgot-password": "Send Email", "Auth.form.button.go-home": "GO BACK HOME", - "Auth.form.button.login": "Log in", + "Auth.form.button.login": "Login", "Auth.form.button.login.providers.error": "We cannot connect you through the selected provider.", "Auth.form.button.login.providers.see-more": "See more", "Auth.form.button.login.strapi": "LOG IN VIA STRAPI", @@ -34,14 +34,19 @@ "Auth.form.lastname.label": "Last name", "Auth.form.lastname.placeholder": "Doe", "Auth.form.password.label": "Password", + "Auth.form.password.show-password": "Show password", + "Auth.form.password.hide-password": "Hide password", "Auth.form.register.news.label": "Keep me updated about the new features and upcoming improvements (by doing this you accept the {terms} and the {policy}).", "Auth.form.rememberMe.label": "Remember me", "Auth.form.username.label": "Username", "Auth.form.username.placeholder": "Kai Doe", + "Auth.form.welcome.subtitle": "Log in to your Strapi account", + "Auth.form.welcome.title": "Welcome back!", "Auth.link.forgot-password": "Forgot your password?", "Auth.link.ready": "Ready to sign in?", "Auth.link.signin": "Sign in", "Auth.link.signin.account": "Already have an account?", + "Auth.login.sso.divider": "Or login with", "Auth.privacy-policy-agreement.policy": "privacy policy", "Auth.privacy-policy-agreement.terms": "terms", "Content Manager": "Content Manager", @@ -316,7 +321,7 @@ "components.Input.error.contentTypeName.taken": "This name already exists", "components.Input.error.custom-error": "{errorMessage} ", "components.Input.error.password.noMatch": "Passwords do not match", - "components.Input.error.validation.email": "This is not an email", + "components.Input.error.validation.email": "This is an invalid email", "components.Input.error.validation.json": "This doesn't match the JSON format", "components.Input.error.validation.max": "The value is too high.", "components.Input.error.validation.maxLength": "The value is too long.", diff --git a/packages/core/admin/ee/admin/pages/AuthPage/components/Login/index.js b/packages/core/admin/ee/admin/pages/AuthPage/components/Login/index.js index cc9ce8020e..fefca630df 100644 --- a/packages/core/admin/ee/admin/pages/AuthPage/components/Login/index.js +++ b/packages/core/admin/ee/admin/pages/AuthPage/components/Login/index.js @@ -1,73 +1,48 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useTheme } from 'styled-components'; -import { Link } from 'react-router-dom'; +import styled from 'styled-components'; +import { Stack, Row, Divider, TableLabel, Box } from '@strapi/parts'; import { useIntl } from 'react-intl'; -import { Flex, Padded, Separator } from '@buffetjs/core'; -import { LoadingIndicator, Tooltip } from '@buffetjs/styles'; -import { Dots } from '@buffetjs/icons'; -import { BaselineAlignment } from '@strapi/helper-plugin'; import BaseLogin from '../../../../../../admin/src/pages/AuthPage/components/Login/BaseLogin'; -import ProviderButton from '../../../../components/ProviderButton'; -import { - ProviderButtonWrapper, - ProviderLink, -} from '../../../../components/ProviderButton/ProviderButtonStyles'; import { useAuthProviders } from '../../../../hooks'; +import UnauthenticatedLayout from '../../../../../../admin/src/layouts/UnauthenticatedLayout'; +import SSOProviders from '../Providers/SSOProviders'; + +const DividerFull = styled(Divider)` + flex: 1; +`; const Login = loginProps => { const ssoEnabled = strapi.features.isEnabled(strapi.features.SSO); - - const theme = useTheme(); const { isLoading, data: providers } = useAuthProviders({ ssoEnabled }); const { formatMessage } = useIntl(); if (!ssoEnabled || (!isLoading && providers.length === 0)) { - return ; + return ( + + + + ); } return ( - - - - - - {isLoading ? ( - - ) : ( - - {providers.slice(0, 2).map((provider, index) => ( - - - - ))} - {providers.length > 2 && ( - - - - - - - - - )} - - )} - - + + + + + + + + + {formatMessage({ id: 'Auth.login.sso.divider' })} + + + + + + + + ); }; diff --git a/packages/core/admin/ee/admin/pages/AuthPage/components/Providers/SSOProviders.js b/packages/core/admin/ee/admin/pages/AuthPage/components/Providers/SSOProviders.js new file mode 100644 index 0000000000..4c9620694e --- /dev/null +++ b/packages/core/admin/ee/admin/pages/AuthPage/components/Providers/SSOProviders.js @@ -0,0 +1,113 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Text, Row, Grid, GridItem, Tooltip } from '@strapi/parts'; +import styled from 'styled-components'; +import { useIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; + +const SSOButton = styled.a` + width: ${136 / 16}rem; + display: flex; + justify-content: center; + align-items: center; + height: ${48 / 16}rem; + border: 1px solid ${({ theme }) => theme.colors.neutral150}; + border-radius: ${({ theme }) => theme.borderRadius}; + text-decoration: inherit; + &:link { + text-decoration: none; + } + color: ${({ theme }) => theme.colors.neutral600}; +`; + +const SSOProvidersWrapper = styled(Row)` + & a:not(:first-child):not(:last-child) { + margin: 0 ${({ theme }) => theme.spaces[2]}; + } + & a:first-child { + margin-right: ${({ theme }) => theme.spaces[2]}; + } + & a:last-child { + margin-left: ${({ theme }) => theme.spaces[2]}; + } +`; + +const SSOProviderButton = ({ provider }) => { + return ( + + + {provider.icon ? ( + + ) : ( + {provider.displayName} + )} + + + ); +}; + +SSOProviderButton.propTypes = { + provider: PropTypes.shape({ + icon: PropTypes.string, + displayName: PropTypes.string.isRequired, + uid: PropTypes.string.isRequired, + }).isRequired, +}; + +const SSOProviders = ({ providers, displayAllProviders }) => { + const { formatMessage } = useIntl(); + + if (displayAllProviders) { + return ( + + {providers.map(provider => ( + + + + ))} + + ); + } + + if (providers.length > 2 && !displayAllProviders) { + return ( + + {providers.slice(0, 2).map(provider => ( + + + + ))} + + + + ••• + + + + + ); + } + + return ( + + {providers.map(provider => ( + + ))} + + ); +}; + +SSOProviders.defaultProps = { + displayAllProviders: true, +}; + +SSOProviders.propTypes = { + providers: PropTypes.arrayOf(PropTypes.object).isRequired, + displayAllProviders: PropTypes.bool, +}; + +export default SSOProviders; diff --git a/packages/core/admin/index.html b/packages/core/admin/index.html index ff86160ebd..b6d254f0e7 100644 --- a/packages/core/admin/index.html +++ b/packages/core/admin/index.html @@ -1,16 +1,16 @@ - + - - - - - - - Strapi Admin - - - -
- - + + + + + + + Strapi Admin + + + +
+ + diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 03cdc38911..c560c8c6a9 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -41,8 +41,8 @@ "@strapi/babel-plugin-switch-ee-ce": "1.0.0", "@strapi/helper-plugin": "3.6.6", "@strapi/utils": "3.6.6", - "@strapi/icons": "0.0.1-alpha.5", - "@strapi/parts": "0.0.1-alpha.5", + "@strapi/icons": "0.0.1-alpha.6", + "@strapi/parts": "0.0.1-alpha.6", "axios": "^0.21.1", "babel-loader": "8.2.2", "babel-plugin-styled-components": "1.12.0", diff --git a/packages/core/helper-plugin/lib/src/testUtils/commonTrads.json b/packages/core/helper-plugin/lib/src/testUtils/commonTrads.json index 36eb78d19e..300edca348 100644 --- a/packages/core/helper-plugin/lib/src/testUtils/commonTrads.json +++ b/packages/core/helper-plugin/lib/src/testUtils/commonTrads.json @@ -109,7 +109,7 @@ "components.Input.error.attribute.taken": "This field name already exists", "components.Input.error.contentTypeName.taken": "This name already exists", "components.Input.error.custom-error": "{errorMessage} ", - "components.Input.error.validation.email": "This is not an email", + "components.Input.error.validation.email": "This is an invalid email", "components.Input.error.validation.json": "This doesn't match the JSON format", "components.Input.error.validation.max": "The value is too high.", "components.Input.error.validation.maxLength": "The value is too long.", diff --git a/test/config/front/testUtils/commonTrads.json b/test/config/front/testUtils/commonTrads.json index e4aa37751b..0569a5827e 100644 --- a/test/config/front/testUtils/commonTrads.json +++ b/test/config/front/testUtils/commonTrads.json @@ -109,7 +109,7 @@ "components.Input.error.attribute.taken": "This field name already exists", "components.Input.error.contentTypeName.taken": "This name already exists", "components.Input.error.custom-error": "{errorMessage} ", - "components.Input.error.validation.email": "This is not an email", + "components.Input.error.validation.email": "This is an invalid email", "components.Input.error.validation.json": "This doesn't match the JSON format", "components.Input.error.validation.max": "The value is too high.", "components.Input.error.validation.maxLength": "The value is too long.", diff --git a/yarn.lock b/yarn.lock index d85e671d6d..b158bdf6dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3490,15 +3490,15 @@ tslib "^2.0.0" upath "2.0.1" -"@strapi/icons@0.0.1-alpha.5": - version "0.0.1-alpha.5" - resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.5.tgz#04f2d62a516e8da6b14f0b1bacc040b10c5f44db" - integrity sha512-PCTQXIkBxfV/qWQZPrbABEdkLSYO7w4+RWVHA7eNlSkcKPP/b5Q/ZEW+56GZ/o9DLexivsGdZaC/aVUzK7G7IA== +"@strapi/icons@0.0.1-alpha.6": + version "0.0.1-alpha.6" + resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.6.tgz#d714c4f0f44d5a53b813989f0c890af62278ce1b" + integrity sha512-QB5ghVyTh+vWlFAbDMmZGS//0+mZLWnB0ejxZfHjvT/d3ByVDycxsugLpk+jwALmD7pPdcqK66+3vR9o97Pl1g== -"@strapi/parts@0.0.1-alpha.5": - version "0.0.1-alpha.5" - resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.5.tgz#6abddbcf4ee58da506065aa49f736b0b51e9cbfa" - integrity sha512-Vb55oaD0G9N9OzsVM4SB3WG30w4Mo3oDITq9lCvwQT9uR0xKuN+GwYK1XdlBTItLcjnJOpRJ3vSoI7sgI1L8rQ== +"@strapi/parts@0.0.1-alpha.6": + version "0.0.1-alpha.6" + resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.6.tgz#7259e26edb7b4195352713a73f23d54dfefc79c8" + integrity sha512-4RhcguoPf41tJ6TbrHrUffOMEmWIJGgT9dxgqy5aFLZFHXnO1x1463Sw5hVSqdT+D05SiJLcBCO3zY+NhpEsDQ== dependencies: compute-scroll-into-view "^1.0.17" prop-types "^15.7.2"