feat: add otp api
This commit is contained in:
@@ -83,7 +83,15 @@ export function SubmitSection({ onSubmit, loading, error, success }: Props) {
|
||||
>
|
||||
{t('completion.registerButton')}
|
||||
</Button> */}
|
||||
<Button variant="contained" onClick={onSubmit} disabled={loading}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={onSubmit}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
width: { xs: '100%', sm: '247px' },
|
||||
alignSelf: { xs: 'stretch', sm: 'center' },
|
||||
}}
|
||||
>
|
||||
{loading
|
||||
? t('completion.submitting')
|
||||
: success
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { Box, Typography, Button } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Logo from '@/components/Logo';
|
||||
import { PersonalInfoFields } from './PersonalInfoFields';
|
||||
@@ -7,11 +7,10 @@ import { PasswordSection } from './PasswordSection';
|
||||
import { EmailSection } from './EmailSection';
|
||||
import { SubmitSection } from './SubmitSection';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { loginWithPassword } from '@/lib/authToken';
|
||||
import { sendEmailOtp, fetchAuthToken } from '@/lib/authToken';
|
||||
|
||||
export function UserCompletionForm() {
|
||||
const { t } = useTranslation('completionForm');
|
||||
const USERNAME = '+989353989651';
|
||||
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
@@ -28,12 +27,13 @@ export function UserCompletionForm() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [codeSent, setCodeSent] = useState(false);
|
||||
const [verificationCode, setVerificationCode] = useState('');
|
||||
const [buttonState, setButtonState] = useState<
|
||||
'default' | 'counting' | 'sent'
|
||||
>('default');
|
||||
const [countdown, setCountdown] = useState(60);
|
||||
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
|
||||
'default',
|
||||
);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [emailVerified, setEmailVerified] = useState(false);
|
||||
const [isVerifyingCode, setIsVerifyingCode] = useState(false);
|
||||
|
||||
const correctEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
|
||||
const matchPassword = password === confirmPassword;
|
||||
@@ -65,10 +65,18 @@ export function UserCompletionForm() {
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (buttonState === 'counting' && countdown > 0) {
|
||||
timer = setInterval(() => setCountdown((prev) => prev - 1), 1000);
|
||||
}
|
||||
if (countdown === 0) {
|
||||
setButtonState('default');
|
||||
timer = setInterval(
|
||||
() =>
|
||||
setCountdown((prev) => {
|
||||
if (prev <= 1) {
|
||||
setButtonState('default');
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
}),
|
||||
1000,
|
||||
);
|
||||
}
|
||||
return () => clearInterval(timer);
|
||||
}, [buttonState, countdown]);
|
||||
@@ -77,46 +85,58 @@ export function UserCompletionForm() {
|
||||
str.replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[parseInt(d, 10)]);
|
||||
|
||||
const getButtonLabel = () => {
|
||||
if (buttonState === 'sent') return t('completion.sent');
|
||||
if (buttonState === 'counting') {
|
||||
const minutes = String(Math.floor(countdown / 60)).padStart(2, '0');
|
||||
const seconds = String(countdown % 60).padStart(2, '0');
|
||||
return toPersianDigits(`${minutes}:${seconds}`);
|
||||
const m = String(Math.floor(countdown / 60)).padStart(2, '0');
|
||||
const s = String(countdown % 60).padStart(2, '0');
|
||||
return toPersianDigits(`${m}:${s}`);
|
||||
}
|
||||
return t('completion.vericationCodeButton');
|
||||
};
|
||||
|
||||
const handleSendCode = () => {
|
||||
setCodeSent(true);
|
||||
setButtonState('sent');
|
||||
setTimeout(() => {
|
||||
setButtonState('counting');
|
||||
setCountdown(60);
|
||||
}, 500);
|
||||
const handleSendCode = async () => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
setSuccess(false);
|
||||
|
||||
try {
|
||||
await sendEmailOtp(EMAIL);
|
||||
setSuccess(true);
|
||||
} catch {
|
||||
setError('Failed to send OTP');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
const handleVerifyCode = async () => {
|
||||
if (!verificationCode) return;
|
||||
setIsVerifyingCode(true);
|
||||
setTimeout(() => {
|
||||
setIsVerifyingCode(false);
|
||||
setError(null);
|
||||
try {
|
||||
const tokenRes = await fetchAuthToken(email, verificationCode);
|
||||
localStorage.setItem('authToken', tokenRes.access_token);
|
||||
apiClient.defaults.headers.common['Authorization'] =
|
||||
`Bearer ${tokenRes.access_token}`;
|
||||
setEmailVerified(true);
|
||||
}, 500);
|
||||
} catch {
|
||||
setError('Invalid verification code');
|
||||
} finally {
|
||||
setIsVerifyingCode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditEmail = () => {
|
||||
setButtonState('default');
|
||||
setCodeSent(false);
|
||||
setEmailVerified(false);
|
||||
setVerificationCode('');
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
|
||||
try {
|
||||
await loginWithPassword(USERNAME, password);
|
||||
|
||||
const { data } = await apiClient.post<{
|
||||
success: boolean;
|
||||
errorCode: number;
|
||||
@@ -132,20 +152,24 @@ export function UserCompletionForm() {
|
||||
saveEmail: showEmail,
|
||||
email: showEmail ? email : undefined,
|
||||
birthDate,
|
||||
country,
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
setError(data.message || 'Validation error');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'An error occurred');
|
||||
setError(
|
||||
err.response?.data?.message || err.message || 'An error occurred',
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const EMAIL = 'klahouti81@gmail.com';
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -232,6 +256,10 @@ export function UserCompletionForm() {
|
||||
handleEditEmail={handleEditEmail}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSendCode} variant="contained" disabled={loading}>
|
||||
{loading ? 'Sending…' : 'Send OTP'}
|
||||
</Button>
|
||||
|
||||
<SubmitSection
|
||||
onSubmit={handleSubmit}
|
||||
loading={loading}
|
||||
|
||||
@@ -1,33 +1,54 @@
|
||||
// src/lib/authService.ts
|
||||
import axios from 'axios';
|
||||
|
||||
export interface SendEmailOtpResponse {
|
||||
success: boolean;
|
||||
errorCode: number;
|
||||
message: string;
|
||||
validations: {
|
||||
message: string;
|
||||
code: number;
|
||||
property: string;
|
||||
severity: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
const authClient = axios.create({
|
||||
baseURL: 'https://account.business-harmony.com',
|
||||
timeout: 10000,
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
const SEND_EMAIL_OTP_URL =
|
||||
'https://account.business-harmony.com/api/User/SendEmailOtp';
|
||||
const TOKEN_URL = 'https://account.business-harmony.com/connect/token';
|
||||
|
||||
export async function loginWithPassword(
|
||||
username: string,
|
||||
password: string,
|
||||
export async function sendEmailOtp(
|
||||
email: string,
|
||||
): Promise<SendEmailOtpResponse> {
|
||||
const { data } = await axios.post<SendEmailOtpResponse>(SEND_EMAIL_OTP_URL, {
|
||||
email,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchAuthToken(
|
||||
email: string,
|
||||
otpCode: string,
|
||||
): Promise<TokenResponse> {
|
||||
const body = new URLSearchParams();
|
||||
body.set('grant_type', 'password');
|
||||
body.set('username', username);
|
||||
body.set('password', password);
|
||||
body.set('client_id', 'harmony_identity');
|
||||
body.set('scope', 'openid harmony_identity profile offline_access');
|
||||
await sendEmailOtp(email);
|
||||
|
||||
const { data } = await authClient.post<TokenResponse>(
|
||||
'/connect/token',
|
||||
body.toString(),
|
||||
);
|
||||
const body = new URLSearchParams({
|
||||
grant_type: 'otp',
|
||||
client_id: 'harmony_identity',
|
||||
phonenumber: '',
|
||||
email,
|
||||
otp_code: otpCode,
|
||||
scope: 'openid profile offline_access harmony_identity',
|
||||
}).toString();
|
||||
|
||||
const { data } = await axios.post<TokenResponse>(TOKEN_URL, body, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
});
|
||||
|
||||
localStorage.setItem('authToken', data.access_token);
|
||||
return data;
|
||||
|
||||
Reference in New Issue
Block a user