@krowdy/kds-auth
Version:
React components that implement Google's Material Design.
505 lines (469 loc) • 15.5 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
import clsx from 'clsx';
import { Button, Checkbox, makeStyles, TextField, FormControlLabel, IconButton, Typography, CircularProgress } from '@krowdy/kds-core';
import { Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon } from '@material-ui/icons';
import GoogleButton from './GoogleButton';
import MicrosoftButton from './MicrosoftButton';
import { useAuth } from '../utils';
import { ViewType } from '../AuthContext/utils';
const inputLabels = {
login: 'Correo o celular',
newPassword: 'Nueva contraseña',
'only-email': 'Correo electrónico',
'only-phone': 'Número de celular',
password: 'Contraseña',
'phone-and-email': 'Correo o celular',
verify: 'Código de verificación'
};
const errorMessages = {
login: 'Error al verificar cuenta, comunícate con soporte.',
newCode: 'Código caducado. Enviamos un nuevo código a tu correo.',
newPassword: 'Debe tener mínimo 8 caracteres.',
password: 'Contraseña incorrecta. Vuelve a intentarlo o haz click en “¿Olvidaste tu contraseña?”',
verify: 'Código de verificación erróneo.'
};
var _ref = /*#__PURE__*/React.createElement(VisibilityIcon, {
color: "inherit",
fontSize: "inherit"
});
var _ref2 = /*#__PURE__*/React.createElement(VisibilityOffIcon, {
color: "inherit",
fontSize: "inherit"
});
var _ref3 = /*#__PURE__*/React.createElement(GoogleButton, null);
var _ref4 = /*#__PURE__*/React.createElement(MicrosoftButton, null);
var _ref5 = /*#__PURE__*/React.createElement(CircularProgress, {
disableShrink: true,
size: 18
});
const KrowdyOneTap = forwardRef(({
onChangeUserLogin = () => {},
typeView,
currentUser
}, ref) => {
const {
verifyAccount,
loginByPassword,
loginByCode,
typeCode,
onSuccessLogin,
updateAccount,
loading,
loginWith,
onUpdatePassword,
onUpdateState,
onChangeView,
onError,
logout
} = useAuth();
const classes = useStyles();
const [loginkey, setLoginKey] = useState(null);
const [valueInput, setValueInput] = useState(typeView === ViewType.Login ? currentUser : '');
const [passwordValue, setPasswordValue] = useState('');
const [isErrorLogin, setErrorLogin] = useState(false);
const [isCodeValidationExpired, setIsCodeValidationExpired] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [keepSession, setKeepSession] = useState(true);
const [register, setRegister] = useState({});
const isNextDisabled = useMemo(() => {
switch (typeView) {
case ViewType.NewPassword:
return passwordValue.length < 8;
case ViewType.Register:
{
const {
firstName,
lastName
} = register;
return !(firstName && lastName);
}
case ViewType.Login:
if (loginWith === 'only-email') {
const emailRegex = /^[-\w.%+]{1,64}@(?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63}$/i;
return !valueInput || !emailRegex.test(valueInput);
} else {
return !valueInput;
}
case ViewType.Password:
case ViewType.Recovery:
return !passwordValue;
default:
return false;
}
}, [typeView, valueInput, register, loginWith, passwordValue]);
const _handleChangeInput = useCallback(({
target: {
value,
name
}
}) => {
setValueInput(value);
if (name === 'login' && value) {
const res = value.split(new RegExp('[ @ | . ]'));
const domain = res ? res[res.length - 2] : null;
if (domain && 'gmail'.indexOf(domain) !== -1) setLoginKey('google');else if (domain && ('outlook'.indexOf(domain) !== -1 || 'hotmail'.indexOf(domain) !== -1)) setLoginKey('microsoft');else setLoginKey(null);
}
}, []);
const _handleChangePassword = useCallback(({
target: {
value
}
}) => {
setPasswordValue(value);
}, []);
const _handleChangeRegister = useCallback(({
target: {
value,
name
}
}) => {
setRegister(prev => _extends({}, prev, {
[name]: value
}));
}, []);
const _handleSubmitSetEmail = useCallback(async (valueInput, nextView) => {
if (nextView && ![ViewType.Password, ViewType.Verify].includes(nextView)) throw new Error('nextView is no valid');
const {
hasPassword,
success,
value,
type
} = await verifyAccount(valueInput, Boolean(nextView));
setErrorLogin(!success);
if (success) {
onChangeUserLogin(type === 'phone' ? value : valueInput);
onChangeView(nextView !== null && nextView !== void 0 ? nextView : hasPassword ? ViewType.Password : ViewType.Verify);
setLoginKey(null);
}
}, [onChangeUserLogin, onChangeView, verifyAccount]);
const _handleNext = useCallback(async e => {
if (e.charCode && e.charCode !== 13 || isNextDisabled || loading) return;
setIsCodeValidationExpired(false);
try {
switch (typeView) {
case ViewType.Login:
{
_handleSubmitSetEmail(valueInput);
break;
}
case ViewType.Password:
{
const {
success: isPasswordValid
} = await loginByPassword({
email: currentUser,
keepSession,
password: passwordValue
});
setErrorLogin(!isPasswordValid);
break;
}
case ViewType.Verify:
{
const {
success: isCodeValid,
isNew: isFirstTime,
error
} = await loginByCode({
code: passwordValue,
keepSession,
type: typeCode,
value: currentUser
});
setErrorLogin(!isCodeValid);
if (error === 'Validation code expired') setIsCodeValidationExpired(!isCodeValid);
if (isCodeValid) if (isFirstTime) onChangeView(ViewType.Register);else onSuccessLogin(true);
break;
}
case ViewType.Register:
{
const {
success: successRegister
} = (await updateAccount(register)) || {};
if (successRegister) {
setValueInput('');
setPasswordValue('');
}
break;
}
case ViewType.NewPassword:
{
const {
success: successPasswword
} = await onUpdatePassword(passwordValue);
if (!successPasswword) setErrorLogin(true);
break;
}
case ViewType.Recovery:
{
const {
success: isRecoveryValid
} = await loginByCode({
code: passwordValue,
keepSession,
type: typeCode,
value: currentUser
});
setErrorLogin(!isRecoveryValid);
if (isRecoveryValid) {
setPasswordValue('');
onChangeView(ViewType.NewPassword);
}
break;
}
default:
break;
}
} catch (error) {
onError(error, typeView);
}
}, [isNextDisabled, loading, typeView, _handleSubmitSetEmail, valueInput, loginByPassword, currentUser, keepSession, passwordValue, loginByCode, typeCode, onChangeView, onSuccessLogin, updateAccount, register, onUpdatePassword, onError]);
const _handleClickShowPassword = useCallback(() => {
setShowPassword(prev => !prev);
}, []);
const _handleKeepSession = useCallback(() => {
setKeepSession(prev => {
onUpdateState({
keepSession: !prev
});
return !prev;
});
}, [onUpdateState]);
const _handleResendPassword = useCallback(async () => {
const {
success
} = await verifyAccount(currentUser, true);
if (success) setErrorLogin(false);
}, [currentUser, verifyAccount]);
const _handleForgotPassword = useCallback(() => {
setPasswordValue('');
onChangeView(ViewType.Recovery);
verifyAccount(valueInput, true); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [verifyAccount, valueInput]);
useImperativeHandle(ref, () => ({
submitEmail: (email, nextView) => {
logout();
_handleChangeInput({
target: {
name: 'email',
value: email
}
});
_handleSubmitSetEmail(email, nextView);
}
}));
return /*#__PURE__*/React.createElement(React.Fragment, null, [ViewType.Password, ViewType.Verify, ViewType.Recovery, ViewType.NewPassword].includes(typeView) && /*#__PURE__*/React.createElement(TextField, {
autoFocus: true,
className: clsx(classes.labelOutlined),
error: isErrorLogin || isCodeValidationExpired,
FormHelperTextProps: {
classes: {
contained: classes.helperText
}
},
fullWidth: true,
helperText: isCodeValidationExpired ? errorMessages.newCode : (isErrorLogin || [ViewType.Recovery, ViewType.NewPassword].includes(typeView)) && errorMessages[typeView],
InputProps: {
className: classes.inputStyle,
classes: {
input: classes.emailInput,
root: clsx(isErrorLogin && classes.colorError)
},
endAdornment: [ViewType.Password, ViewType.NewPassword].includes(typeView) && /*#__PURE__*/React.createElement(IconButton, {
className: classes.iconShowPassword,
onClick: _handleClickShowPassword,
size: "small"
}, showPassword ? _ref : _ref2)
},
label: [ViewType.Verify, ViewType.Recovery].includes(typeView) ? 'Código de verificación' : inputLabels[typeView],
onChange: _handleChangePassword,
onKeyPress: _handleNext,
required: true,
type: showPassword || [ViewType.Verify, ViewType.Recovery].includes(typeView) ? 'text' : 'password',
value: passwordValue,
variant: "outlined"
}), typeView === ViewType.Login && /*#__PURE__*/React.createElement(TextField, {
autoFocus: true,
className: clsx(classes.fieldEmail, classes.labelOutlined),
error: isErrorLogin,
fullWidth: true,
helperText: isErrorLogin && errorMessages[typeView],
InputProps: {
classes: {
input: classes.emailInput,
root: classes.textfield
}
},
label: inputLabels[loginWith],
name: "login",
onChange: _handleChangeInput,
onKeyPress: _handleNext,
type: "email",
value: valueInput,
variant: "outlined"
}), typeView === ViewType.Register && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextField, {
autoFocus: true,
className: clsx(classes.labelOutlined),
fullWidth: true // InputLabelProps={{
// classes: {
// root: classes.labelOutlined
// }
// }}
,
InputProps: {
classes: {
input: classes.emailInput,
root: classes.textfield
}
},
label: "Nombres",
name: "firstName",
onChange: _handleChangeRegister,
required: true,
value: register.firstName || '',
variant: "outlined"
}), /*#__PURE__*/React.createElement(TextField, {
className: clsx(classes.fieldEmail, classes.labelOutlined),
fullWidth: true // InputLabelProps={{
// classes: {
// root: classes.labelOutlined
// }
// }}
,
InputProps: {
classes: {
input: classes.emailInput,
root: classes.textfield
}
},
label: "Apellidos",
name: "lastName",
onChange: _handleChangeRegister,
required: true,
value: register.lastName || '',
variant: "outlined"
}), /*#__PURE__*/React.createElement(TextField, {
className: clsx(classes.fieldEmail, classes.labelOutlined),
fullWidth: true // InputLabelProps={{
// classes: {
// root: classes.labelOutlined
// }
// }}
,
InputProps: {
classes: {
input: classes.emailInput,
root: classes.textfield
}
},
label: currentUser.indexOf('@') !== -1 ? 'Celular (opcional)' : 'Correo electrónico (opcional)',
name: currentUser.indexOf('@') !== -1 ? 'phone' : 'email',
onChange: _handleChangeRegister,
value: register[currentUser.indexOf('@') !== -1 ? 'phone' : 'email'] || '',
variant: "outlined"
})), loginkey && /*#__PURE__*/React.createElement("div", {
className: classes.margintop
}, loginkey === 'google' && _ref3, loginkey === 'microsoft' && _ref4), [ViewType.Password, ViewType.Verify, ViewType.NewPassword].includes(typeView) && /*#__PURE__*/React.createElement(FormControlLabel, {
className: classes.labelCheckbox,
control: /*#__PURE__*/React.createElement(Checkbox, {
checked: keepSession,
color: "primary",
onChange: _handleKeepSession
}),
label: "Mantener mi sesi\xF3n abierta"
}), typeView === ViewType.NewPassword && /*#__PURE__*/React.createElement(Typography, {
align: "center",
className: clsx(classes.textfield, classes.textEnd, classes.nextButton2),
color: "disabled"
}, "No olvides revisar tu contrase\xF1a antes de establecerla para evitar cualquier inconveniente m\xE1s adelante."), /*#__PURE__*/React.createElement(Button, {
className: clsx(classes.nextButton, loginkey && classes.nextButton2),
color: "primary",
disabled: isNextDisabled || loading,
fullWidth: true,
onClick: _handleNext,
startIcon: loading && _ref5,
variant: "contained"
}, "Continuar"), [ViewType.Password, ViewType.Verify, ViewType.Recovery].includes(typeView) && /*#__PURE__*/React.createElement("div", {
className: classes.endContent
}, typeView === ViewType.Password ? /*#__PURE__*/React.createElement(Button, {
color: "primary",
onClick: _handleForgotPassword,
size: "small"
}, "\xBFOlvidaste tu contrase\xF1a?") : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Typography, {
className: clsx(classes.textfield, classes.textEnd),
color: "disabled"
}, "\xBFNo lo encuentras?"), /*#__PURE__*/React.createElement(Button, {
className: classes.capitalizeText,
color: "primary",
onClick: _handleResendPassword,
size: "small"
}, "Reenviar"))));
});
const useStyles = makeStyles(({
spacing,
palette
}) => ({
capitalizeText: {
textTransform: 'capitalize'
},
colorError: {
color: palette.error.main
},
emailInput: {
color: palette.common.black,
// fontSize: 14,
padding: spacing(1.5)
},
endContent: {
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
marginTop: spacing(3)
},
fieldEmail: {
marginTop: spacing(1.5)
},
helperText: {
marginLeft: 0
},
inputStyle: {
fontSize: 14
},
labelCheckbox: {
'& > span': {
fontSize: 14,
padding: spacing(1, 1, 1, 0)
},
margin: spacing(2, 0, 0, 0)
},
labelOutlined: {
'& .MuiInputLabel-outlined': {
transform: 'translate(8px, 13px) scale(1)'
},
'& .MuiInputLabel-outlined.MuiInputLabel-shrink': {
transform: 'translate(8px, -5px) scale(0.9)'
}
},
margintop: {
marginTop: spacing(4)
},
nextButton: {
marginTop: spacing(4)
},
nextButton2: {
marginTop: spacing(2)
},
outlinedLabel: {
top: -8
},
textEnd: {
color: palette.grey[600]
},
textfield: {
fontSize: 14
}
}), {
name: 'KrowdyOneTap'
});
export default React.memo(KrowdyOneTap);