import { User, Tenant } from '@memori.ai/memori-api-client/dist/types'; import React, { useEffect, useState } from 'react'; import Button from '../ui/Button'; import Drawer from '../ui/Drawer'; import toast from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import cx from 'classnames'; import memoriApiClient from '@memori.ai/memori-api-client'; import { getErrori18nKey } from '../../helpers/error'; import { mailRegEx, pwdRegEx } from '../../helpers/utils'; export interface Props { open?: boolean; onClose: () => void; user?: User; loginToken?: string; onLogin: (user: User, token: string) => void; onLogout: () => void; tenant: Tenant; apiClient: ReturnType; __TEST__signup?: boolean; __TEST__needMissingData?: boolean; setUser: (user: User) => void; /** Optional class for the drawer root (e.g. for z-index when layout is WEBSITE_ASSISTANT). */ drawerClassName?: string; } const LoginDrawer = ({ open = false, onClose, onLogin, user, loginToken, setUser, tenant, apiClient, __TEST__signup = false, __TEST__needMissingData = false, drawerClassName, }: Props) => { const { t, i18n } = useTranslation(); const lang = i18n.language === 'it' ? 'it' : 'en'; const { pwlUpdateUser, loginWithOTP, validateOTPCode, pwlGetCurrentUser, uploadAsset, } = apiClient.backend; const isUserLoggedIn = user?.userID && loginToken; const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // OTP-related state const [otpCode, setOtpCode] = useState(''); const [otpEmail, setOtpEmail] = useState(''); const [otpError, setOtpError] = useState(null); const [showOtpForm, setShowOtpForm] = useState(true); const [showOtpCodeForm, setShowOtpCodeForm] = useState(false); const [otpTimer, setOtpTimer] = useState(null); const [otpSent, setOtpSent] = useState(false); const [avatar, setAvatar] = useState(null); const [isResending, setIsResending] = useState(false); const [otpSuccess, setOtpSuccess] = useState(false); const [emailValid, setEmailValid] = useState(false); const [birthDate, setBirthDate] = useState(''); const [tnCAndPPAccepted, setTnCAndPPAccepted] = useState(false); const [pAndCUAccepted, setPAndCUAccepted] = useState(false); const [showSignup, setShowSignup] = useState(__TEST__signup); const [needsMissingData, setNeedsMissingData] = useState<{ token: string; birthDate?: boolean; tnCAndPPAccepted?: boolean; }>( __TEST__needMissingData ? { token: 'token', birthDate: true, tnCAndPPAccepted: true, } : ({} as any) ); // OTP timer effect useEffect(() => { let interval: NodeJS.Timeout; if (otpTimer && otpTimer > 0) { interval = setInterval(() => { setOtpTimer(prev => (prev && prev > 0 ? prev - 1 : 0)); }, 1000); } return () => { if (interval) clearInterval(interval); }; }, [otpTimer]); // Send OTP to email const sendOtpToEmail = async (email: string, isResend = false) => { if (!email || !mailRegEx.test(email)) { setOtpError(t('login.emailInvalid')); return; } if (isResend) { setIsResending(true); } else { setLoading(true); } setOtpError(null); try { const response = await loginWithOTP({ tenant: tenant.name, eMail: email.trim(), }); if (response.resultCode === 0) { toast.success(isResend ? t('login.otpResent') : t('login.otpSent')); setOtpEmail(email.trim()); setOtpSent(true); setShowOtpCodeForm(true); startOtpTimer(); setOtpSuccess(false); } else { setOtpError(response.resultMessage || t('login.otpSendError')); } } catch (err) { console.error('[OTP SEND]', err); setOtpError(t('login.otpSendError')); } finally { setLoading(false); setIsResending(false); } }; // Validate OTP code const validateOtp = async (otp: string) => { if (!otp || otp.length !== 4) { setOtpError(t('login.otpInvalidFormat')); return; } if (!otpEmail || otpEmail.trim().length === 0) { setOtpError(t('login.emailRequired')); return; } setLoading(true); setOtpError(null); try { const response = await validateOTPCode( otp, tenant.name, undefined, otpEmail.trim() ); if (response.resultCode === 0) { setOtpSuccess(true); toast.success(t('login.otpSuccess')); // Add a small delay for better UX setTimeout(async () => { try { // Call the get user with the new token const { user, ...resp } = await pwlGetCurrentUser( response.newSessionToken ); if (!user.age || !user.tnCAndPPAccepted) { setUser(user); setBirthDate(user?.birthDate ?? ''); setTnCAndPPAccepted(user?.tnCAndPPAccepted ?? false); setPAndCUAccepted(user?.pAndCUAccepted ?? false); setNeedsMissingData({ token: response.newSessionToken, birthDate: !user.age, tnCAndPPAccepted: !user.tnCAndPPAccepted, }); } else { onLogin(user, response.newSessionToken); } } catch (err) { console.error('[GET USER]', err); toast.error(t('login.userFetchError')); } }, 1000); // Reset states setTimeout(() => { setShowOtpForm(false); setShowOtpCodeForm(false); setOtpCode(''); setOtpEmail(''); setOtpSent(false); setOtpSuccess(false); }, 2000); } else { // Handle specific error codes if (response.resultCode === -107) { setOtpError(t('login.otpNotFound')); } else if (response.resultCode === -108) { setOtpError(t('login.otpExpired')); } else if (response.resultCode === -109) { setOtpError(t('login.otpMissing')); } else { setOtpError(response.resultMessage || t('login.otpInvalid')); } } } catch (err) { console.error('[OTP VALIDATION]', err); setOtpError(t('login.otpError')); } finally { setLoading(false); } }; // Handle OTP input change const handleOtpChange = (value: string) => { const numericValue = value.replace(/\D/g, '').slice(0, 4); setOtpCode(numericValue); setOtpError(null); if (numericValue.length === 4 && otpEmail.trim().length > 0) { validateOtp(numericValue); } }; // Handle email input change const handleEmailChange = (value: string) => { setOtpEmail(value); setOtpError(null); setEmailValid(mailRegEx.test(value)); }; // Handle resend OTP const handleResendOtp = () => { if (otpEmail && mailRegEx.test(otpEmail)) { sendOtpToEmail(otpEmail, true); } }; // Start OTP timer const startOtpTimer = () => { setOtpTimer(60); }; const updateMissingData = async (e: React.FormEvent) => { e.preventDefault(); if (!user?.userID || !needsMissingData?.token) { setError(t('login.userNotFound')); return; } if (!birthDate || !tnCAndPPAccepted) { setError(t('missingData')); return; } let newUser: Partial = { userID: user.userID, birthDate: user?.birthDate || !needsMissingData.birthDate ? undefined : birthDate, tnCAndPPAccepted: tnCAndPPAccepted || user?.tnCAndPPAccepted, tnCAndPPAcceptanceDate: tnCAndPPAccepted ? new Date().toISOString() : undefined, pAndCUAccepted: pAndCUAccepted || user?.pAndCUAccepted, pAndCUAcceptanceDate: pAndCUAccepted ? new Date().toISOString() : undefined, }; const { user: patchedUser, ...resp } = await pwlUpdateUser( needsMissingData.token, user.userID, newUser ); if (resp.resultCode !== 0) { console.error(resp); toast.error(t(getErrori18nKey(resp.resultCode))); setError(resp.resultMessage); } else { toast.success(t('success')); onLogin(patchedUser || newUser, needsMissingData.token); } }; return ( {needsMissingData?.token?.length ? ( <>

{t('login.missingData')}

{t('login.missingDataHelper')}

{needsMissingData.birthDate && ( <>

{t('login.birthDateHelper')}

)} {needsMissingData?.tnCAndPPAccepted && ( <>

{t('login.goToAccountToChangeYourPreferences')}

{t('login.deepThoughtExplaination')}

)} {error && (

{error}

)}
) : showOtpCodeForm ? ( <>
{otpSuccess && (
)}

{t('login.otpTitle')}

{otpSuccess ? t('login.otpSuccessMessage') : t('login.otpCodeDescription', { email: otpEmail })}

{!otpSuccess && (
)} {otpTimer && otpTimer > 0 && !otpSuccess && (
{/* ⏱️ */} {t('login.otpTimer', { seconds: otpTimer })}
)} {!otpSuccess && (
)} {otpError && (
⚠️ {otpError}
)}
) : showOtpForm ? ( <>
{/*
📧
*/}

{t('login.otpEmailTitle')}

{t('login.otpEmailDescription')}

{showOtpCodeForm && }
{otpError && (
⚠️ {otpError}
)}
) : null}
); }; export default LoginDrawer;