Wire SMS registration, forgot password, and one-time login.

Connect business and customer login pages to login-otp and reset-password, and document the auth flows in project context.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-05 15:10:51 +03:30
co-authored by Cursor
parent d8c9c0b017
commit c33cab40fe
12 changed files with 148 additions and 114 deletions
+27 -38
View File
@@ -4,7 +4,6 @@ import { Eye, EyeOff, Smartphone, Lock, KeyRound, ArrowLeft, User } from 'lucide
import {
useAuth,
CUSTOMER_ACCESS_MESSAGE,
VERIFICATION_REQUIRED_MESSAGE,
} from '../context/AuthContext'
import { useTenantBranding } from '../context/TenantBrandingContext'
import { ApiError } from '../lib/api'
@@ -13,8 +12,8 @@ import { getTenantDomain } from '../lib/config'
import {
logout as logoutRequest,
register,
resetPassword,
sendOtp,
verifyOtp,
} from '../services/authService'
import { LanguageSelect } from '@meshkee/dashboard-ui'
import { useT } from '../i18n/useT'
@@ -35,7 +34,7 @@ export function LoginPage() {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const redirectTo = safeRedirectPath(searchParams.get('redirect'))
const { login } = useAuth()
const { login, loginWithOtp } = useAuth()
const { businessName, logoUrl } = useTenantBranding()
const tenantDomain = getTenantDomain()
const t = useT()
@@ -172,11 +171,24 @@ export function LoginPage() {
}
if (!data.user.cellVerifiedAt) {
setInfo(
`${VERIFICATION_REQUIRED_MESSAGE} Use one-time login with SMS to verify your number.`,
)
switchView('otp')
setPhone(phone)
logoutRequest()
setView('otp')
setSmsStep('phone')
setPassword('')
setConfirmPassword('')
setSmsCode('')
setInfo(t('signup.info.verify'))
try {
const otpResult = await sendOtp(cellNumber)
if (!otpResult.enabled) {
setInfo(otpResult.message)
}
setCodeSent(true)
setSmsStep('code')
startCountdown()
} catch (otpErr) {
handleApiError(otpErr, t('login.error.sendCode'))
}
return
}
@@ -202,9 +214,9 @@ export function LoginPage() {
try {
const cellNumber = toE164CellNumber(phone)
await verifyOtp(cellNumber, smsCode)
setInfo(t('forgot.info.partial'))
setTimeout(() => switchView('login'), 2500)
await resetPassword(cellNumber, smsCode, newPassword)
setInfo(t('forgot.info.success'))
setTimeout(() => switchView('login'), 2000)
} catch (err) {
handleApiError(err, t('forgot.error.verify'))
} finally {
@@ -219,14 +231,7 @@ export function LoginPage() {
try {
const cellNumber = toE164CellNumber(phone)
await verifyOtp(cellNumber, smsCode)
if (!password) {
setError(t('otp.error.password'))
return
}
await login(cellNumber, password)
await loginWithOtp(cellNumber, smsCode)
navigate(redirectTo)
} catch (err) {
handleApiError(err, t('otp.error.signIn'))
@@ -619,6 +624,7 @@ export function LoginPage() {
{error}
</div>
)}
{info && <div className={styles.info}>{info}</div>}
{smsStep === 'phone' ? (
<>
@@ -645,7 +651,7 @@ export function LoginPage() {
onClick={() => void handleSendCode()}
disabled={isSubmitting}
>
{isSubmitting ? t('forgot.sending') : t('forgot.sendCode')}
{isSubmitting ? t('otp.sending') : t('otp.sendCode')}
</button>
</>
) : (
@@ -674,23 +680,6 @@ export function LoginPage() {
</div>
</div>
<div className={styles.field}>
<label htmlFor="otp-password">{t('otp.password')}</label>
<div className={styles.inputWrap}>
<Lock size={18} className={styles.inputIcon} />
<input
id="otp-password"
type={showPassword ? 'text' : 'password'}
placeholder={t('otp.passwordPlaceholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
disabled={isSubmitting}
/>
</div>
</div>
<div className={styles.resendRow}>
{countdown > 0 ? (
<span className={styles.countdown}>{t('common.resendIn', { seconds: countdown })}</span>
@@ -707,7 +696,7 @@ export function LoginPage() {
</div>
<button type="submit" className={styles.submitBtn} disabled={isSubmitting}>
{isSubmitting ? t('login.signingIn') : t('login.signIn')}
{isSubmitting ? t('otp.signingIn') : t('otp.signIn')}
</button>
</>
)}