mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
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:
co-authored by
Cursor
parent
d8c9c0b017
commit
c33cab40fe
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user