This commit is contained in:
pradeepkumar
2026-03-31 23:23:16 +05:30
parent f7f308af84
commit 719404b663
5 changed files with 55 additions and 40 deletions

View File

@@ -297,11 +297,19 @@ export default function AgentDashboard() {
const handleSubmitVerification = async () => { const handleSubmitVerification = async () => {
setIsSubmittingVerification(true); setIsSubmittingVerification(true);
try { try {
await usersService.submitForVerification(); const result = await usersService.submitForVerification();
// Update local state console.log('Verification submit result:', result);
// Update local state immediately
if (agentProfile) { if (agentProfile) {
setAgentProfile({ ...agentProfile, verificationStatus: 'PENDING_REVIEW', verificationNote: null } as AgentProfile); setAgentProfile({ ...agentProfile, verificationStatus: 'PENDING_REVIEW', verificationNote: null } as AgentProfile);
} }
// Also re-fetch profile to confirm
try {
const freshProfile = await agentsService.getProfile();
setAgentProfile(freshProfile);
} catch {
// Local state already updated
}
} catch (err: any) { } catch (err: any) {
const data = err?.response?.data; const data = err?.response?.data;
if (data?.missingFields && Array.isArray(data.missingFields)) { if (data?.missingFields && Array.isArray(data.missingFields)) {
@@ -328,18 +336,9 @@ export default function AgentDashboard() {
<p className="font-serif text-[13px] text-red-700 mt-1">Reason: {agentProfile.verificationNote}</p> <p className="font-serif text-[13px] text-red-700 mt-1">Reason: {agentProfile.verificationNote}</p>
)} )}
<p className="font-serif text-[13px] text-red-600 mt-2">Please update your profile and documents, then resubmit for verification.</p> <p className="font-serif text-[13px] text-red-600 mt-2">Please update your profile and documents, then resubmit for verification.</p>
<div className="flex gap-2 mt-2"> <Link href="/agent/edit" className="inline-block mt-2 px-4 py-1.5 bg-[#e58625] text-white rounded-[10px] font-serif text-[13px] hover:bg-[#d47920] transition-colors">
<Link href="/agent/edit" className="inline-block px-4 py-1.5 border border-[#e58625] text-[#e58625] rounded-[10px] font-serif text-[13px] hover:bg-[#e58625]/5 transition-colors"> Edit & Resubmit
Edit Profile </Link>
</Link>
<button
onClick={handleSubmitVerification}
disabled={isSubmittingVerification}
className="px-4 py-1.5 bg-[#e58625] text-white rounded-[10px] font-serif text-[13px] hover:bg-[#d47920] transition-colors disabled:opacity-50"
>
{isSubmittingVerification ? 'Submitting...' : 'Submit for Review'}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -352,24 +351,15 @@ export default function AgentDashboard() {
</div> </div>
</div> </div>
)} )}
{agentProfile?.verificationStatus === 'NONE' && ( {(!agentProfile?.verificationStatus || agentProfile?.verificationStatus === 'NONE') && (
<div className="bg-blue-50 border border-blue-200 rounded-[15px] p-4"> <div className="bg-blue-50 border border-blue-200 rounded-[15px] p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Image src="/assets/icons/caution-icon.svg" alt="Info" width={20} height={20} className="flex-shrink-0" /> <Image src="/assets/icons/caution-icon.svg" alt="Info" width={20} height={20} className="flex-shrink-0" />
<div className="flex-1"> <div className="flex-1">
<p className="font-fractul font-bold text-[14px] text-blue-800">Complete your profile and upload verification documents to get verified.</p> <p className="font-fractul font-bold text-[14px] text-blue-800">Complete your profile to get verified.</p>
<div className="flex gap-2 mt-2"> <Link href="/agent/edit" className="inline-block mt-2 px-4 py-1.5 bg-[#e58625] text-white rounded-[10px] font-serif text-[13px] hover:bg-[#d47920] transition-colors">
<Link href="/agent/edit" className="inline-block px-4 py-1.5 border border-[#e58625] text-[#e58625] rounded-[10px] font-serif text-[13px] hover:bg-[#e58625]/5 transition-colors"> Complete Profile
Complete Profile </Link>
</Link>
<button
onClick={handleSubmitVerification}
disabled={isSubmittingVerification}
className="px-4 py-1.5 bg-[#e58625] text-white rounded-[10px] font-serif text-[13px] hover:bg-[#d47920] transition-colors disabled:opacity-50"
>
{isSubmittingVerification ? 'Submitting...' : 'Submit for Review'}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,6 +7,7 @@ import DynamicSection from './components/DynamicSection';
import RepeatableSection, { RepeatableEntryData } from './components/RepeatableSection'; import RepeatableSection, { RepeatableEntryData } from './components/RepeatableSection';
import { profileSectionsService, ProfileSection, AgentTypeSectionsResponse } from '@/services/profile-sections.service'; import { profileSectionsService, ProfileSection, AgentTypeSectionsResponse } from '@/services/profile-sections.service';
import { agentsService, AgentProfile, FieldValueInput } from '@/services/agents.service'; import { agentsService, AgentProfile, FieldValueInput } from '@/services/agents.service';
import { usersService } from '@/services/users.service';
import { MobileBackButton } from '@/components/layout/MobileBackButton'; import { MobileBackButton } from '@/components/layout/MobileBackButton';
export default function EditProfilePage() { export default function EditProfilePage() {
@@ -38,6 +39,12 @@ export default function EditProfilePage() {
const profile = await agentsService.getMyProfile(); const profile = await agentsService.getMyProfile();
setAgentProfile(profile); setAgentProfile(profile);
// Block editing when verification is under review
if (profile.verificationStatus === 'PENDING_REVIEW') {
router.push('/agent/dashboard');
return;
}
if (!profile.agentTypeId) { if (!profile.agentTypeId) {
setError('Your profile does not have an agent type assigned. Please contact support.'); setError('Your profile does not have an agent type assigned. Please contact support.');
setLoading(false); setLoading(false);
@@ -276,28 +283,31 @@ export default function EditProfilePage() {
await agentsService.saveFieldValues(fieldValues); await agentsService.saveFieldValues(fieldValues);
// Also update main profile fields (phone, email) to keep them in sync // Also update main profile fields (phone, email) to keep them in sync
// This ensures that when the view page checks agentProfile.phone first,
// it gets the correct (possibly empty) value
const profileUpdates: Partial<{ phone: string | null; email: string | null }> = {}; const profileUpdates: Partial<{ phone: string | null; email: string | null }> = {};
// Sync phone field - check dynamic field slugs FIRST, then legacy 'phone'
// Dynamic fields (phone_number, etc.) are the source of truth from the form
// 'phone' is a legacy pre-populated key and should only be used as fallback
const phoneFieldSlugs = ['phone_number', 'cell_number', 'office_number', 'phone']; const phoneFieldSlugs = ['phone_number', 'cell_number', 'office_number', 'phone'];
for (const slug of phoneFieldSlugs) { for (const slug of phoneFieldSlugs) {
const phoneValue = formData[slug]; const phoneValue = formData[slug];
if (phoneValue !== undefined) { if (phoneValue !== undefined) {
// Set to null if empty, otherwise use the value
profileUpdates.phone = phoneValue === '' ? null : (phoneValue as string); profileUpdates.phone = phoneValue === '' ? null : (phoneValue as string);
break; // Use the first found phone field break;
} }
} }
// Only update if there are changes to sync
if (Object.keys(profileUpdates).length > 0) { if (Object.keys(profileUpdates).length > 0) {
await agentsService.updateProfile(profileUpdates); await agentsService.updateProfile(profileUpdates);
} }
// Auto-submit for verification (status: NONE or REJECTED → PENDING_REVIEW)
const status = agentProfile?.verificationStatus;
if (!status || status === 'NONE' || status === 'REJECTED') {
try {
await usersService.submitForVerification();
} catch {
// If submit fails (e.g. already pending), still proceed
}
}
router.push('/agent/dashboard'); router.push('/agent/dashboard');
} catch (err) { } catch (err) {
console.error('Failed to save:', err); console.error('Failed to save:', err);
@@ -444,7 +454,7 @@ export default function EditProfilePage() {
{isSaving && ( {isSaving && (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
)} )}
{isSaving ? 'Saving...' : 'Save'} {isSaving ? 'Submitting...' : 'Submit for Review'}
</button> </button>
</div> </div>
)} )}

View File

@@ -1,13 +1,14 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { signIn } from 'next-auth/react'; import { signIn } from 'next-auth/react';
import Link from 'next/link'; import Link from 'next/link';
import { resetLogoutState } from '@/services/api'; import { resetLogoutState } from '@/services/api';
export default function LoginPage() { export default function LoginPage() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
@@ -19,7 +20,12 @@ export default function LoginPage() {
useEffect(() => { useEffect(() => {
localStorage.removeItem('isLoggingOut'); localStorage.removeItem('isLoggingOut');
resetLogoutState(); resetLogoutState();
}, []); // Show error from social login redirect
const errorParam = searchParams.get('error');
if (errorParam) {
setError(errorParam);
}
}, [searchParams]);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -90,6 +96,8 @@ export default function LoginPage() {
}; };
const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => { const handleSocialLogin = (provider: 'google' | 'facebook' | 'twitter') => {
// Set login mode so backend rejects if user doesn't exist
document.cookie = `socialAuthMode=login;path=/;max-age=300;SameSite=Lax`;
signIn(provider, { callbackUrl: '/' }); signIn(provider, { callbackUrl: '/' });
}; };

View File

@@ -99,7 +99,8 @@ export default function SignUpPage() {
setError('Please select an agent type before signing up'); setError('Please select an agent type before signing up');
return; return;
} }
// Set cookies with selected role and agent type so server-side auth callback can read them // Set cookies with mode, role and agent type so server-side auth callback can read them
document.cookie = `socialAuthMode=signup;path=/;max-age=300;SameSite=Lax`;
document.cookie = `socialAuthRole=${userType};path=/;max-age=300;SameSite=Lax`; document.cookie = `socialAuthRole=${userType};path=/;max-age=300;SameSite=Lax`;
if (userType === 'AGENT' && selectedAgentTypeId) { if (userType === 'AGENT' && selectedAgentTypeId) {
document.cookie = `socialAuthAgentTypeId=${selectedAgentTypeId};path=/;max-age=300;SameSite=Lax`; document.cookie = `socialAuthAgentTypeId=${selectedAgentTypeId};path=/;max-age=300;SameSite=Lax`;

View File

@@ -117,11 +117,13 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
// Read role and agent type from cookies (set by signup page before OAuth redirect) // Read role and agent type from cookies (set by signup page before OAuth redirect)
let role: string | undefined; let role: string | undefined;
let agentTypeId: string | undefined; let agentTypeId: string | undefined;
let mode: string | undefined;
try { try {
const { cookies } = await import("next/headers"); const { cookies } = await import("next/headers");
const cookieStore = await cookies(); const cookieStore = await cookies();
role = cookieStore.get("socialAuthRole")?.value; role = cookieStore.get("socialAuthRole")?.value;
agentTypeId = cookieStore.get("socialAuthAgentTypeId")?.value; agentTypeId = cookieStore.get("socialAuthAgentTypeId")?.value;
mode = cookieStore.get("socialAuthMode")?.value;
} catch { } catch {
// Cookie may not be available // Cookie may not be available
} }
@@ -139,6 +141,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
avatar: user.image, avatar: user.image,
role, role,
agentTypeId, agentTypeId,
mode: mode || 'signup',
}), }),
} }
); );
@@ -150,6 +153,9 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
(user as any).role = data.data.user.role; (user as any).role = data.data.user.role;
(user as any).accessToken = data.data.accessToken; (user as any).accessToken = data.data.accessToken;
(user as any).refreshToken = data.data.refreshToken; (user as any).refreshToken = data.data.refreshToken;
} else if (!res.ok) {
const errorMsg = data?.message || "Social login failed";
return `/login?error=${encodeURIComponent(errorMsg)}`;
} }
} catch (error) { } catch (error) {
console.error("Social auth sync error:", error); console.error("Social auth sync error:", error);