UNPKG

@krowdy/kds-auth

Version:

React components that implement Google's Material Design.

505 lines (469 loc) 15.5 kB
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);