Move auth views to the admin

This commit is contained in:
soupette 2019-09-09 13:58:07 +02:00
parent b7bbca6bfc
commit 1cd7084a94
43 changed files with 309 additions and 99 deletions

View File

@ -9,6 +9,7 @@ import {
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
GET_APP_PLUGINS_SUCCEEDED,
GET_DATA_SUCCEEDED,
LOAD_PLUGIN,
PLUGIN_DELETED,
PLUGIN_LOADED,
@ -36,6 +37,13 @@ export function freezeApp(data) {
};
}
export function getDataSucceeded(hasAdminUser) {
return {
type: GET_DATA_SUCCEEDED,
hasAdminUser,
};
}
export function getAppPluginsSucceeded(plugins) {
return {
type: GET_APP_PLUGINS_SUCCEEDED,

View File

@ -16,3 +16,4 @@ export const DISABLE_GLOBAL_OVERLAY_BLOCKER =
'app/App/OverlayBlocker/DISABLE_GLOBAL_OVERLAY_BLOCKER';
export const ENABLE_GLOBAL_OVERLAY_BLOCKER =
'app/App/OverlayBlocker/ENABLE_GLOBAL_OVERLAY_BLOCKER';
export const GET_DATA_SUCCEEDED = 'app/App/OverlayBlocker/GET_DATA_SUCCEEDED';

View File

@ -11,9 +11,12 @@
* the linting exception.
*/
import React from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
import { LoadingIndicatorPage } from 'strapi-helper-plugin';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { LoadingIndicatorPage, request } from 'strapi-helper-plugin';
import Admin from '../Admin';
import NotFoundPage from '../NotFoundPage';
@ -22,12 +25,32 @@ import AppLoader from '../AppLoader';
import styles from './styles.scss';
import AuthPage from '../AuthPage';
import { getDataSucceeded } from './actions';
function App(props) {
const getDataRef = useRef();
getDataRef.current = props.getDataSucceeded;
useEffect(() => {
const getData = async () => {
try {
const requestURL = '/users-permissions/init';
const { hasAdmin } = await request(requestURL, { method: 'GET' });
getDataRef.current(hasAdmin);
} catch (err) {
strapi.notification.error('app.containers.App.notification.error.init');
}
};
getData();
}, [getDataRef]);
return (
<div>
<NotificationProvider />
<AppLoader>
{({ shouldLoad }) => {
{({ hasAdminUser, shouldLoad }) => {
if (shouldLoad) {
return <LoadingIndicatorPage />;
}
@ -38,7 +61,11 @@ function App(props) {
<Route
path="/auth/:authType"
render={routerProps => (
<AuthPage {...props} {...routerProps} />
<AuthPage
{...props}
{...routerProps}
hasAdminUser={hasAdminUser}
/>
)}
exact
/>
@ -56,6 +83,17 @@ function App(props) {
);
}
App.propTypes = {};
App.propTypes = {
getDataSucceeded: PropTypes.func.isRequired,
};
export default App;
export function mapDispatchToProps(dispatch) {
return bindActionCreators({ getDataSucceeded }, dispatch);
}
const withConnect = connect(
null,
mapDispatchToProps
);
export default compose(withConnect)(App);

View File

@ -6,6 +6,7 @@ import {
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
GET_APP_PLUGINS_SUCCEEDED,
GET_DATA_SUCCEEDED,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
@ -17,8 +18,11 @@ const initialState = fromJS({
appPlugins: List([]),
blockApp: false,
overlayBlockerData: null,
// TODO remove when new lifecycle
hasUserPlugin: true,
hasAdminUser: false,
isAppLoading: true,
isLoading: true,
plugins: {},
showGlobalAppBlocker: true,
});
@ -41,12 +45,16 @@ function appReducer(state = initialState, action) {
return state
.update('appPlugins', () => List(action.appPlugins))
.update('isAppLoading', () => false);
case GET_DATA_SUCCEEDED:
return state
.update('isLoading', () => false)
.update('hasAdminUser', () => action.hasAdminUser);
case PLUGIN_LOADED:
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
case UPDATE_PLUGIN:
return state.setIn(
['plugins', action.pluginId, action.updatedKey],
fromJS(action.updatedValue),
fromJS(action.updatedValue)
);
case PLUGIN_DELETED:
return state.deleteIn(['plugins', action.plugin]);

View File

@ -11,21 +11,27 @@ import makeSelectApp from '../App/selectors';
export class AppLoader extends React.Component {
shouldLoad = () => {
const { appPlugins, plugins: mountedPlugins } = this.props;
const { appPlugins, isLoading, plugins: mountedPlugins } = this.props;
if (isLoading) {
return true;
}
return appPlugins.length !== Object.keys(mountedPlugins).length;
};
render() {
const { children } = this.props;
const { children, hasAdminUser } = this.props;
return children({ shouldLoad: this.shouldLoad() });
return children({ hasAdminUser, shouldLoad: this.shouldLoad() });
}
}
AppLoader.propTypes = {
appPlugins: PropTypes.array.isRequired,
children: PropTypes.func.isRequired,
hasAdminUser: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
plugins: PropTypes.object.isRequired,
};
@ -33,5 +39,5 @@ const mapStateToProps = makeSelectApp();
export default connect(
mapStateToProps,
null,
null
)(AppLoader);

View File

@ -1,6 +1,7 @@
import React, { memo } from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { InputsIndex as Inputs } from 'strapi-helper-plugin';
import CustomLabel from './CustomLabel';
@ -8,8 +9,11 @@ import CustomLabel from './CustomLabel';
const Input = ({
autoFocus,
customBootstrapClass,
didCheckErrors,
errors,
label,
name,
noErrorsDescription,
onChange,
placeholder,
type,
@ -58,10 +62,11 @@ const Input = ({
<Inputs
autoFocus={autoFocus}
customBootstrapClass={customBootstrapClass || 'col-12'}
didCheckErrors={false}
errors={{}}
didCheckErrors={didCheckErrors}
errors={get(errors, name, [])}
label={inputLabel}
name={name}
noErrorsDescription={noErrorsDescription}
onChange={onChange}
placeholder={placeholder}
type={type}
@ -74,8 +79,11 @@ const Input = ({
Input.propTypes = {
autoFocus: PropTypes.bool,
customBootstrapClass: PropTypes.string,
didCheckErrors: PropTypes.bool.isRequired,
errors: PropTypes.object.isRequired,
label: PropTypes.object,
name: PropTypes.string.isRequired,
noErrorsDescription: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
type: PropTypes.string.isRequired,

View File

@ -40,7 +40,9 @@ const Wrapper = styled.div`
text-align: center;
margin: auto;
// padding: 13px 30px 0 30px;
padding: 8px 30px 0 30px;
padding: ${({ authType }) =>
authType === 'register' ? '13px 30px 17px 30px' : '8px 30px 0 30px'};
line-height: 18px;
color: #333740;
}
@ -85,7 +87,8 @@ const Wrapper = styled.div`
}
.bordered {
border-top: 2px solid #1c5de7;
border-top: 2px solid
${({ withSucessBorder }) => (withSucessBorder ? '#5a9e06' : '#1c5de7')};
}
.borderedSuccess {

View File

@ -3,7 +3,6 @@ import { translatedErrors } from 'strapi-helper-plugin';
const form = {
'forgot-password': {
endPoint: '/auth/forgot-password',
inputs: [
[
{
@ -24,7 +23,6 @@ const form = {
}),
},
login: {
endPoint: '/auth/local',
inputs: [
[
{
@ -62,7 +60,6 @@ const form = {
}),
},
register: {
endPoint: '/auth/local/register',
inputs: [
[
{
@ -126,26 +123,14 @@ const form = {
passwordConfirmation: yup
.string()
.min(6, translatedErrors.minLength)
.oneOf([yup.ref('password'), null])
.required({ id: 'components.Input.error.password.noMatch' }),
.oneOf(
[yup.ref('password'), null],
'components.Input.error.password.noMatch'
)
.required(translatedErrors.required),
}),
},
'register-success': {
inputs: [
[
{
name: 'email',
type: 'email',
label: {
id: 'Auth.form.register-success.email.label',
},
placeholder: 'Auth.form.register-success.email.placeholder',
},
],
],
},
'reset-password': {
endPoint: '/auth/reset-password',
inputs: [
[
{
@ -175,8 +160,11 @@ const form = {
passwordConfirmation: yup
.string()
.min(6, translatedErrors.required)
.oneOf([yup.ref('password'), null])
.required({ id: 'components.Input.error.password.noMatch' }),
.oneOf(
[yup.ref('password'), null],
'components.Input.error.password.noMatch'
)
.required(translatedErrors.required),
}),
},
};

View File

@ -1,9 +1,16 @@
import React, { memo, useReducer } from 'react';
import React, { memo, useEffect, useReducer, useRef } from 'react';
import PropTypes from 'prop-types';
import { get, isEmpty, omit, set } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { upperFirst } from 'lodash';
import { Link, Redirect } from 'react-router-dom';
import { Button, getYupInnerErrors } from 'strapi-helper-plugin';
import {
auth,
Button,
getQueryParameters,
getYupInnerErrors,
request,
} from 'strapi-helper-plugin';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import LogoStrapi from '../../assets/images/logo_strapi.png';
import PageTitle from '../../components/PageTitle';
@ -12,14 +19,43 @@ import Wrapper from './Wrapper';
import Input from './Input';
import forms from './forms';
import reducer, { initialState } from './reducer';
import formatErrorFromRequest from './utils/formatErrorFromRequest';
const AuthPage = ({
hasAdminUser,
history: { push },
location: { search },
match: {
params: { authType },
},
}) => {
const [reducerState, dispatch] = useReducer(reducer, initialState);
const { modifiedData } = reducerState.toJS();
const codeRef = useRef();
codeRef.current = getQueryParameters(search, 'code');
useEffect(() => {
// Set the reset code provided by the url
if (authType === 'reset-password') {
dispatch({
type: 'ON_CHANGE',
keys: ['code'],
value: codeRef.current,
});
} else {
// Clean reducer upon navigation
dispatch({
type: 'RESET_PROPS',
});
}
return () => {};
}, [authType, codeRef]);
const {
didCheckErrors,
errors,
modifiedData,
submitSuccess,
userEmail,
} = reducerState.toJS();
const handleChange = ({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
@ -27,20 +63,63 @@ const AuthPage = ({
value,
});
};
const handleSubmit = async e => {
e.preventDefault();
const schema = forms[authType].schema;
let errors = {};
let formErrors = {};
try {
await schema.validate(modifiedData, { abortEarly: false });
try {
const requestEndPoint = authType === 'login' ? 'local' : authType;
const requestURL = `/admin/auth/${requestEndPoint}`;
const body = omit(modifiedData, 'news');
if (authType === 'forgot-password') {
set(body, 'url', `${strapi.backendURL}/admin/auth/reset-password`);
}
const { jwt, user, ok } = await request(requestURL, {
method: 'POST',
body,
});
if (authType === 'forgot-password' && ok === true) {
dispatch({
type: 'SUBMIT_SUCCESS',
email: modifiedData.email,
});
} else {
auth.setToken(jwt, modifiedData.rememberMe);
auth.setUserInfo(user, modifiedData.rememberMe);
push('/');
}
} catch (err) {
const formattedError = formatErrorFromRequest(err);
if (authType === 'login') {
formErrors = {
global: formattedError,
identifier: formattedError,
password: formattedError,
};
} else if (authType === 'forgot-password') {
formErrors = { email: formattedError };
} else {
strapi.notification.error(
get(formattedError, '0.id', 'notification.error')
);
}
}
} catch (err) {
errors = getYupInnerErrors(err);
formErrors = getYupInnerErrors(err);
}
dispatch({
type: 'SET_ERRORS',
errors,
formErrors,
});
};
@ -49,13 +128,17 @@ const AuthPage = ({
return <Redirect to="/" />;
}
// TODO Remove temporary
const hasErrors = false;
if (hasAdminUser && authType === 'register') {
return <Redirect to="/auth/login" />;
}
const globalError = get(errors, 'global.0.id', '');
const shouldShowFormErrors = !isEmpty(globalError);
return (
<>
<PageTitle title={upperFirst(authType)} />
<Wrapper>
<Wrapper authType={authType} withSucessBorder={submitSuccess}>
<NavTopRightWrapper>
<LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
</NavTopRightWrapper>
@ -76,25 +159,36 @@ const AuthPage = ({
<div className="formContainer bordered">
<form onSubmit={handleSubmit}>
<div className="container-fluid">
{/* TODO ERROR CONTAINER */}
{hasErrors && (
{shouldShowFormErrors && (
<div className="errorsContainer">
{/* TODO DISPLAY ERRORS */}
<FormattedMessage id={globalError} />
</div>
)}
<div className="row" style={{ textAlign: 'start' }}>
{forms[authType].inputs.map(row => {
return row.map(input => {
return (
<Input
{...input}
key={input.name}
onChange={handleChange}
value={modifiedData[input.name]}
/>
);
});
})}
{submitSuccess && (
<div className="forgotSuccess">
<FormattedMessage id="Auth.form.forgot-password.email.label.success" />
<br />
<p>{userEmail}</p>
</div>
)}
{!submitSuccess &&
forms[authType].inputs.map((row, index) => {
return row.map(input => {
return (
<Input
{...input}
autoFocus={index === 0}
didCheckErrors={didCheckErrors}
errors={errors}
key={input.name}
noErrorsDescription={shouldShowFormErrors}
onChange={handleChange}
value={modifiedData[input.name]}
/>
);
});
})}
<div
className={`${
authType === 'login'
@ -103,12 +197,13 @@ const AuthPage = ({
}`}
>
<Button
className={submitSuccess ? 'buttonForgotSuccess' : ''}
type="submit"
label="Auth.form.button.login"
primary
style={
authType === 'forgot-password' ? { width: '100%' } : {}
}
label={`Auth.form.button.${
submitSuccess ? 'forgot-password.success' : authType
}`}
primary={!submitSuccess}
style={authType === 'login' ? {} : { width: '100%' }}
/>
</div>
</div>
@ -142,6 +237,13 @@ const AuthPage = ({
};
AuthPage.propTypes = {
hasAdminUser: PropTypes.bool.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}),
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}),
match: PropTypes.shape({
params: PropTypes.shape({
authType: PropTypes.string,

View File

@ -4,6 +4,8 @@ const initialState = fromJS({
didCheckErrors: false,
errors: {},
modifiedData: {},
userEmail: '',
submitSuccess: false,
});
const reducer = (state, action) => {
@ -13,10 +15,16 @@ const reducer = (state, action) => {
['modifiedData', ...action.keys],
() => action.value
);
case 'RESET_PROPS':
return initialState;
case 'SET_ERRORS':
return state
.update('errors', () => action.errors)
.update('errors', () => action.formErrors)
.update('didCheckErrors', v => !v);
case 'SUBMIT_SUCCESS':
return state
.update('userEmail', () => action.email)
.update('submitSuccess', () => true);
default:
return state;
}

View File

@ -0,0 +1,22 @@
import { get } from 'lodash';
const formatErrorFromRequest = errorResponse => {
const messages = get(errorResponse, ['response', 'payload', 'message'], []);
return messages.reduce((acc, current) => {
const err = current.messages.reduce(
(acc, key) => {
acc.id = key.id;
return acc;
},
{ id: '' }
);
acc.push(err);
return acc;
}, []);
};
export default formatErrorFromRequest;

View File

@ -181,5 +181,6 @@
"Auth.form.register.username.placeholder": "اكتب اسمك هنا (مثل: خالد سالم)",
"Auth.header.register.description": "لإنهاء الإعداد وتأمين تطبيقك ، يرجى إنشاء أول مستخدم (مسؤول أساسي) عن طريق إدخال المعلومات الضرورية أدناه.",
"Auth.link.forgot-password": "هل نسيت كلمة السر الخاصة بك؟",
"Auth.link.ready": "مستعد لتسجيل الدخول؟"
"Auth.link.ready": "مستعد لتسجيل الدخول؟",
"components.Input.error.password.noMatch": "كلمات السر لا تتطابق"
}

View File

@ -184,5 +184,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Erstelle bitte einen Administrator durch Ausfüllen der folgenden Felder, um die Einrichtung deiner App abzuschließen.",
"Auth.link.forgot-password": "Passwort vergessen?",
"Auth.link.ready": "Bereit für den Login?"
"Auth.link.ready": "Bereit für den Login?",
"components.Input.error.password.noMatch": "Passwörter stimmen nicht überein"
}

View File

@ -210,5 +210,7 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "To finish setup and secure your app, please create the first user (root admin) by entering the necessary information below.",
"Auth.link.forgot-password": "Forgot your password?",
"Auth.link.ready": "Ready to sign in?"
"Auth.link.ready": "Ready to sign in?",
"app.containers.App.notification.error.init": "An error occured while requesting the API",
"components.Input.error.password.noMatch": "Passwords do not match"
}

View File

@ -199,5 +199,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Para terminar de configurar y asegurar su aplicación, por favor cree el primer usuario (administrador) ingresando a continuación la información necesaria.",
"Auth.link.forgot-password": "¿Olvidó su contraseña?",
"Auth.link.ready": "¿Listo para iniciar sesión?"
"Auth.link.ready": "¿Listo para iniciar sesión?",
"components.Input.error.password.noMatch": "Las contraseñas no coinciden"
}

View File

@ -210,5 +210,7 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Pour finir le setup et sécuriser votre app, merci de créer le premier utilisateur (root admin) en remplissant le formulaire suivant.",
"Auth.link.forgot-password": "Mot de passe oublié ?",
"Auth.link.ready": "Prêt à vous connecter ?"
"Auth.link.ready": "Prêt à vous connecter ?",
"app.containers.App.notification.error.init": "Une erreur est survenue en requêtant l'API",
"components.Input.error.password.noMatch": "Le mot de passe ne correspond pas"
}

View File

@ -181,5 +181,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Per terminare l'installazione e mettere in sicurezza la tua applicazione è necessario creare il primo utente (root admin) inserendo le informazioni necessarie di seguito riportate",
"Auth.link.forgot-password": "Password dimenticata?",
"Auth.link.ready": "Sei pronto per accedere?"
"Auth.link.ready": "Sei pronto per accedere?",
"components.Input.error.password.noMatch": "La password non corrisponde"
}

View File

@ -184,5 +184,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "セットアップを完了してアプリを保護するには、以下の情報を入力して最初のユーザー(ルート管理者)を作成してください。",
"Auth.link.forgot-password": "パスワードをお忘れですか?",
"Auth.link.ready": "サインインする準備ができましたか?"
"Auth.link.ready": "サインインする準備ができましたか?",
"components.Input.error.password.noMatch": "パスワードが一致しません"
}

View File

@ -195,5 +195,6 @@
"Auth.form.register.username.placeholder": "JohnDoe",
"Auth.header.register.description": "애플리케이션 설정 및 보안을 위해 첫 번째 사용자(root 관리자)를 생성하세요.",
"Auth.link.forgot-password": "패스워드 재설정",
"Auth.link.ready": "로그인 하시겠습니까?"
"Auth.link.ready": "로그인 하시겠습니까?",
"components.Input.error.password.noMatch": "패스워드가 일치하지 않습니다."
}

View File

@ -184,5 +184,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Om de set-up af te maken en je app te beveiligen, maak a.u.b. een eerste gebruiker (root admin) door alle velden hier beneden in te vullen.",
"Auth.link.forgot-password": "Wachtwoord vergeten?",
"Auth.link.ready": "Klaar om in te loggen?"
"Auth.link.ready": "Klaar om in te loggen?",
"components.Input.error.password.noMatch": "Wachtwoorden komen niet overeen"
}

View File

@ -183,5 +183,6 @@
"Auth.form.register.username.placeholder": "Jan Nowak",
"Auth.header.register.description": "Aby zakończyć konfigurację i zabezpieczyć swoją aplikację, utwórz pierwszego użytkownika (administratora root), wypełniając poniższe informacje.",
"Auth.link.forgot-password": "Nie pamiętasz hasła?",
"Auth.link.ready": "Zaczynamy?"
"Auth.link.ready": "Zaczynamy?",
"components.Input.error.password.noMatch": "Hasło nie pasuje"
}

View File

@ -181,5 +181,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Para concluir a configuração e proteger seu aplicativo, crie o primeiro usuário (administrador root), digitando as informações necessárias abaixo.",
"Auth.link.forgot-password": "Esqueceu sua senha?",
"Auth.link.ready": "Pronto para entrar?"
"Auth.link.ready": "Pronto para entrar?",
"components.Input.error.password.noMatch": "As senhas não conferem"
}

View File

@ -183,5 +183,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Para terminar a configuração e melhorar a segurança da sua aplicação, por favor crie o primeiro utilizador (root admin) inserindo a informação necessária abaixo.",
"Auth.link.forgot-password": "Esqueceu a palavra-passe?",
"Auth.link.ready": "Preparado para entrar?"
"Auth.link.ready": "Preparado para entrar?",
"components.Input.error.password.noMatch": "As passwords não coincidem"
}

View File

@ -199,5 +199,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Для завершения установки и обеспечения безопасности приложения, создайте вашего первого пользователя (root admin), заполнив форму ниже.",
"Auth.link.forgot-password": "Забыли пароль?",
"Auth.link.ready": "Готовы войти?"
"Auth.link.ready": "Готовы войти?",
"components.Input.error.password.noMatch": "Пароль не совпадает"
}

View File

@ -184,5 +184,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "Kurulumu tamamlamak ve uygulamanızı güvence altına almak için, lütfen aşağıdaki gerekli bilgileri girerek ilk kullanıcıyı (root admin) oluşturun.",
"Auth.link.forgot-password": "Parolanızı mı unuttunuz ?",
"Auth.link.ready": "Zaten kayıtlı mısınız?"
"Auth.link.ready": "Zaten kayıtlı mısınız?",
"components.Input.error.password.noMatch": "Şifreler uyuşmuyor"
}

View File

@ -176,5 +176,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "要完成安装并保护您的应用程序请通过输入必要的信息来创建第一个用户root管理员。",
"Auth.link.forgot-password": "忘记密码了吗?",
"Auth.link.ready": "准备好登陆?"
"Auth.link.ready": "准备好登陆?",
"components.Input.error.password.noMatch": "密码不匹配"
}

View File

@ -184,5 +184,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "為了完成設定,請填寫以下欄位以建立第一個最高權限管理者。",
"Auth.link.forgot-password": "忘記密碼了嗎?",
"Auth.link.ready": "準備登入了嗎?"
"Auth.link.ready": "準備登入了嗎?",
"components.Input.error.password.noMatch": "密碼不符"
}

View File

@ -89,7 +89,7 @@
"PopUpForm.header.edit.email-templates": "تحرير قوالب البريد الإلكتروني",
"PopUpForm.header.edit.providers": "تحرير موفر {provider}",
"PopUpForm.inputSelect.providers.label": "اختر الموفر",
"components.Input.error.password.noMatch": "كلمات السر لا تتطابق",
"notification.error.delete": "حدث خطأ أثناء محاولة حذف العنصر",
"notification.error.fetch": "حدث خطأ أثناء محاولة جلب البيانات",
"notification.error.fetchUser": "حدث خطأ أثناء محاولة جلب المستخدمين",

View File

@ -96,7 +96,7 @@
"PopUpForm.header.edit.email-templates": "E-Mail-Templates bearbeiten",
"PopUpForm.header.edit.providers": "Methode {provider} bearbeiten",
"PopUpForm.inputSelect.providers.label": "Wähle die Methode aus",
"components.Input.error.password.noMatch": "Passwörter stimmen nicht überein",
"components.Input.error.password.length": "Passwort ist zu kurz",
"notification.error.delete": "Beim Löschen des Objekts ist ein Fehler aufgetreten",
"notification.error.fetch": "Beim Abruf von Daten ist ein Fehler aufgetreten",

View File

@ -96,7 +96,7 @@
"PopUpForm.header.edit.email-templates": "Edit Email Templates",
"PopUpForm.header.edit.providers": "Edit {provider} Provider",
"PopUpForm.inputSelect.providers.label": "Choose the provider",
"components.Input.error.password.noMatch": "Passwords do not match",
"components.Input.error.password.length": "Password is too short",
"notification.error.delete": "An error occurred while trying to delete the item",
"notification.error.fetch": "An error occurred while trying to fetch data",

View File

@ -95,7 +95,7 @@
"PopUpForm.header.edit.email-templates": "Editar Plantillas de Email",
"PopUpForm.header.edit.providers": "Editar Proveedor {provider}",
"PopUpForm.inputSelect.providers.label": "Elija el proveedor",
"components.Input.error.password.noMatch": "Las contraseñas no coinciden",
"components.Input.error.password.length": "Contraseña muy corta",
"notification.error.delete": "Se ha producido un error al intentar borrar el elemento",
"notification.error.fetch": "Se ha producido un error al intentar recuperar los datos",

View File

@ -97,7 +97,7 @@
"PopUpForm.header.edit.email-templates": "Editer E-mail Templates",
"PopUpForm.header.edit.providers": "Editer {provider} Provider",
"PopUpForm.inputSelect.providers.label": "Sélectionnez le provider",
"components.Input.error.password.noMatch": "Le mot de passe ne correspond pas",
"components.Input.error.password.length": "Le mot de passe doit contenir au moins 6 caractères",
"notification.error.delete": "Une erreur est survenue en essayant de supprimer cet élément",
"notification.error.fetch": "Une erreur est survenue en essayant de récupérer les données",

View File

@ -87,7 +87,7 @@
"PopUpForm.header.edit.email-templates": "Modifica il template delle Email",
"PopUpForm.header.edit.providers": "Modifica {provider} Provider",
"PopUpForm.inputSelect.providers.label": "Scegli il provider",
"components.Input.error.password.noMatch": "La password non corrisponde",
"eaderNav.link.advancedSettings": "Impostazioni avanzate",
"notification.error.delete": "Si è verificato un errore mentre si stava cercando di eliminare l'elemento",
"notification.error.fetch": "Si è verificato un errore mentre si stava cercando di recuperare i dati",

View File

@ -95,7 +95,7 @@
"PopUpForm.header.edit.email-templates": "メールテンプレートの編集",
"PopUpForm.header.edit.providers": "{provider}プロバイダを編集",
"PopUpForm.inputSelect.providers.label": "プロバイダの選択",
"components.Input.error.password.noMatch": "パスワードが一致しません",
"components.Input.error.password.length": "パスワードが一致しません",
"notification.error.delete": "アイテムの削除中にエラーが発生しました",
"notification.error.fetch": "データの取得中にエラーが発生しました",

View File

@ -89,7 +89,7 @@
"PopUpForm.header.edit.email-templates": "이메일 템플릿 수정",
"PopUpForm.header.edit.providers": "{provider} 프로바이더(Provider) 설정",
"PopUpForm.inputSelect.providers.label": "프로바이더(provider) 선택",
"components.Input.error.password.noMatch": "패스워드가 일치하지 않습니다.",
"notification.error.delete": "항목을 삭제하는데 에러가 발생했습니다.",
"notification.error.fetch": "데이터를 가져오는데 에러가 발생했습니다.",
"notification.error.fetchUser": "사용자를 가져오는데 에러가 발생했습니다.",

View File

@ -97,7 +97,7 @@
"PopUpForm.header.edit.providers": "Leverancier {provider} aanpassen",
"PopUpForm.inputSelect.providers.label": "Kies een leverancier",
"components.Input.error.password.length": "Wachtwoord is te kort",
"components.Input.error.password.noMatch": "Wachtwoorden komen niet overeen",
"notification.error.delete": "Er is een fout opgetreden tijdens het verwijderen van dit item",
"notification.error.fetch": "Er is een fout opgetreden tijdens het ophalen van de data",
"notification.error.fetchUser": "Er is een fout opgetreden tijdens het ophalen van de gebruikers",

View File

@ -96,7 +96,7 @@
"PopUpForm.header.edit.email-templates": "Zmień szablony e-mail",
"PopUpForm.header.edit.providers": "Edytuj dostawcę {provider}",
"PopUpForm.inputSelect.providers.label": "Dostawca",
"components.Input.error.password.noMatch": "Hasło nie pasuje",
"components.Input.error.password.length": "Hasło jest za krótkie",
"notification.error.delete": "Wystąpił błąd podczas usuwania pozycji",
"notification.error.fetch": "Wystąpił błąd podczas pobierania danych",

View File

@ -89,7 +89,7 @@
"PopUpForm.header.edit.email-templates": "Editar modelos de email",
"PopUpForm.header.edit.providers": "Editar provedor {provider}",
"PopUpForm.inputSelect.providers.label": "Escolher o provedor",
"components.Input.error.password.noMatch": "As senhas não conferem",
"notification.error.delete": "Ocorreu um erro ao tentar eliminar o registro",
"notification.error.fetch": "Ocorreu um erro ao tentar buscar dados",
"notification.error.fetchUser": "Ocorreu um erro ao tentar buscar usuários",

View File

@ -95,7 +95,7 @@
"PopUpForm.header.edit.email-templates": "Editar Modelos de Email",
"PopUpForm.header.edit.providers": "Editar o serviço de autenticação {provider}",
"PopUpForm.inputSelect.providers.label": "Selecionar o serviço de autenticação",
"components.Input.error.password.noMatch": "As passwords não coincidem",
"components.Input.error.password.length": "A password é demasiado curta",
"notification.error.delete": "Ocorreu um erro a tentar eliminar o item",
"notification.error.fetch": "Ocorreu um erro a tentar obter os dados",

View File

@ -97,7 +97,7 @@
"PopUpForm.header.edit.email-templates": "Редактировать шаблон письма",
"PopUpForm.header.edit.providers": "Редактирование провайдера {provider}",
"PopUpForm.inputSelect.providers.label": "Выбрать провайдера",
"components.Input.error.password.noMatch": "Пароль не совпадает",
"notification.error.delete": "Возникла ошибка в процессе удаления",
"notification.error.fetch": "Возникла ошибка при загрузке данных",
"notification.error.fetchUser": "Возникла ошибка при загрузке пользователей",

View File

@ -96,7 +96,7 @@
"PopUpForm.header.edit.email-templates": "E-posta Şablonlarını Düzenle",
"PopUpForm.header.edit.providers": "{sağlayıcı} sağlayıcını düzenle",
"PopUpForm.inputSelect.providers.label": "Sağlayıcıyı seçin",
"components.Input.error.password.noMatch": "Şifreler uyuşmuyor",
"components.Input.error.password.length": "Şifreniz çok kısa",
"notification.error.delete": "Öğe silinirken bir hata oluştu",
"notification.error.fetch": "Verileri getirmeye çalışılırken bir hata oluştu",

View File

@ -89,7 +89,7 @@
"PopUpForm.header.edit.email-templates": "编辑电子邮件模版",
"PopUpForm.header.edit.providers": "编译 {provider} 供应商",
"PopUpForm.inputSelect.providers.label": "选择供应商",
"components.Input.error.password.noMatch": "密码不匹配",
"notification.error.delete": "删除数据时出错",
"notification.error.fetch": "获取数据时出错",
"notification.error.fetchUser": "获取用户时出错",

View File

@ -95,7 +95,7 @@
"PopUpForm.header.edit.email-templates": "編輯郵件範本",
"PopUpForm.header.edit.providers": "編輯 {provider} 驗證方式",
"PopUpForm.inputSelect.providers.label": "選擇驗證方式",
"components.Input.error.password.noMatch": "密碼不符",
"components.Input.error.password.length": "密碼長度過短",
"notification.error.delete": "刪除項目時發生錯誤",
"notification.error.fetch": "讀取資料時發生錯誤",