Skip to content

Commit 336ccc0

Browse files
committed
feat: implement KYC status handling and Didit callback integration
- Added a new route for Didit redirect callback to handle user verification status. - Enhanced SettingsContent to reflect verification status upon redirection. - Introduced KYC status management with corresponding badge images in UserMenu and navbar. - Created utility functions for KYC status image retrieval and added new KYC status images.
1 parent b56cb43 commit 336ccc0

8 files changed

Lines changed: 107 additions & 21 deletions

File tree

app/api/didit/callback/route.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
3+
/**
4+
* Didit redirect callback: user is sent here after completing verification on Didit.
5+
* We redirect them to Settings → Identity so they see their updated status.
6+
* The backend receives the final result via webhook; this route only handles the browser redirect.
7+
*/
8+
export async function GET(request: NextRequest) {
9+
const searchParams = request.nextUrl.searchParams;
10+
const verification = searchParams.get('verification') ?? 'complete';
11+
12+
const baseUrl = request.nextUrl.origin;
13+
const redirectUrl = new URL('/me/settings', baseUrl);
14+
redirectUrl.searchParams.set('verification', verification);
15+
16+
return NextResponse.redirect(redirectUrl, 302);
17+
}

app/me/settings/SettingsContent.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import Profile from '@/components/profile/update/Profile';
33
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
44
import { useEffect, useState, useCallback } from 'react';
5+
import { useSearchParams } from 'next/navigation';
56
import { User } from '@/types/user';
67
import { getMe } from '@/lib/api/auth';
78
import { GetMeResponse } from '@/lib/api/types';
@@ -11,6 +12,8 @@ import { IdentityVerificationSection } from '@/components/didit/IdentityVerifica
1112
import { invalidateAuthProfileCache } from '@/hooks/use-auth';
1213

1314
const SettingsContent = () => {
15+
const searchParams = useSearchParams();
16+
const fromVerification = searchParams.get('verification') === 'complete';
1417
const [userData, setUserData] = useState<GetMeResponse | null>(null);
1518
const [isLoading, setIsLoading] = useState(true);
1619

@@ -54,7 +57,10 @@ const SettingsContent = () => {
5457
Manage your personal information and public profile
5558
</p>
5659
</div>
57-
<Tabs defaultValue='profile' className='w-full'>
60+
<Tabs
61+
defaultValue={fromVerification ? 'identity' : 'profile'}
62+
className='w-full'
63+
>
5864
<TabsList className='inline-flex h-auto gap-6 bg-transparent p-0'>
5965
<TabsTrigger
6066
value='profile'

components/landing-page/navbar.tsx

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
} from '../ui/dropdown-menu';
2727
import { UserMenu } from '../user/UserMenu';
2828
import { cn } from '@/lib/utils';
29+
import { getKycImageAndAlt } from '@/lib/kyc-status';
30+
import type { GetMeResponse } from '@/lib/api/types';
2931
import { BoundlessButton } from '../buttons';
3032
import { useProtectedAction } from '@/hooks/use-protected-action';
3133
import WalletRequiredModal from '@/components/wallet/WalletRequiredModal';
@@ -350,6 +352,11 @@ const MobileMenu = ({
350352
const displayName = useMemo(() => {
351353
return user?.name || user?.profile?.firstName || 'User';
352354
}, [user]);
355+
356+
const kycStatus = (user?.profile as GetMeResponse | undefined)?.user
357+
?.identityVerificationStatus;
358+
const kycBadge = getKycImageAndAlt(kycStatus);
359+
353360
return (
354361
<div className='min-[990px]:hidden'>
355362
<Sheet open={isOpen} onOpenChange={setIsOpen}>
@@ -389,18 +396,44 @@ const MobileMenu = ({
389396
<div className='flex flex-1 flex-col gap-6 overflow-y-auto pb-6'>
390397
{isAuthenticated && user && (
391398
<div className='flex items-center gap-3 rounded-lg border border-white/10 bg-white/5 p-4 transition-colors hover:bg-white/8'>
392-
<Avatar className='h-10 w-10 border border-white/10'>
393-
<AvatarImage
394-
src={user.profile?.image || ''}
395-
alt={displayName}
396-
/>
397-
<AvatarFallback className='bg-white/10 text-white/80'>
398-
{userInitial}
399-
</AvatarFallback>
400-
</Avatar>
399+
<span className='relative shrink-0'>
400+
<Avatar className='h-10 w-10 border border-white/10'>
401+
<AvatarImage
402+
src={
403+
(user.profile as GetMeResponse)?.user?.image ||
404+
user.profile?.image ||
405+
''
406+
}
407+
alt={displayName}
408+
/>
409+
<AvatarFallback className='bg-white/10 text-white/80'>
410+
{userInitial}
411+
</AvatarFallback>
412+
</Avatar>
413+
{kycBadge && (
414+
<span className='absolute -right-0.5 -bottom-0.5 flex rounded-full bg-black'>
415+
<Image
416+
src={kycBadge.src}
417+
alt={kycBadge.alt}
418+
width={14}
419+
height={14}
420+
className='h-3.5 w-3.5'
421+
/>
422+
</span>
423+
)}
424+
</span>
401425
<div className='min-w-0 flex-1'>
402-
<p className='truncate text-sm font-medium text-white'>
403-
{displayName}
426+
<p className='flex items-center gap-1.5 truncate text-sm font-medium text-white'>
427+
<span className='truncate'>{displayName}</span>
428+
{kycBadge && (
429+
<Image
430+
src={kycBadge.src}
431+
alt={kycBadge.alt}
432+
width={14}
433+
height={14}
434+
className='h-3.5 w-3.5 shrink-0'
435+
/>
436+
)}
404437
</p>
405438
<p className='truncate text-xs text-white/60'>
406439
{user?.email}

components/user/UserMenu.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Image from 'next/image';
1414
import React, { useContext } from 'react';
1515
import { useAuthActions, useAuthStatus } from '@/hooks/use-auth';
1616
import { OrganizationContext } from '@/lib/providers/OrganizationProvider';
17+
import { getKycImageAndAlt } from '@/lib/kyc-status';
1718

1819
interface UserMenuProps {
1920
/**
@@ -47,8 +48,8 @@ export const UserMenu: React.FC<UserMenuProps> = ({
4748

4849
const orgContext = useContext(OrganizationContext);
4950
const organizations = orgContext?.organizations || [];
50-
const isVerified =
51-
user?.profile?.user?.identityVerificationStatus === 'Approved';
51+
const kycStatus = user?.profile?.user?.identityVerificationStatus;
52+
const kycBadge = getKycImageAndAlt(kycStatus);
5253

5354
return (
5455
<DropdownMenu>
@@ -70,14 +71,14 @@ export const UserMenu: React.FC<UserMenuProps> = ({
7071
{(user?.name || 'U').charAt(0).toUpperCase()}
7172
</AvatarFallback>
7273
</Avatar>
73-
{isVerified && (
74+
{kycBadge && (
7475
<span className='absolute -right-0.5 -bottom-0.5 flex rounded-full bg-black'>
7576
<Image
76-
src='/verified.png'
77-
alt='Verified'
77+
src={kycBadge.src}
78+
alt={kycBadge.alt}
7879
width={12}
7980
height={12}
80-
className='h-4 w-4'
81+
className='h-3 w-3'
8182
/>
8283
</span>
8384
)}
@@ -108,10 +109,10 @@ export const UserMenu: React.FC<UserMenuProps> = ({
108109
<div className='min-w-0 flex-1'>
109110
<p className='flex items-center gap-1.5 truncate text-sm font-semibold text-white'>
110111
<span className='truncate'>{user?.name || 'User'}</span>
111-
{isVerified && (
112+
{kycBadge && (
112113
<Image
113-
src='/verified.png'
114-
alt='Verified'
114+
src={kycBadge.src}
115+
alt={kycBadge.alt}
115116
width={14}
116117
height={14}
117118
className='h-3.5 w-3.5 shrink-0'

lib/kyc-status.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* KYC (identity verification) status and corresponding badge images.
3+
* Used in UserMenu and navbar to show verified / pending / rejected state.
4+
*/
5+
6+
export type KycStatus = 'Approved' | 'Declined' | 'In Review';
7+
8+
export const KYC_STATUS_IMAGE: Record<KycStatus, string> = {
9+
Approved: '/kyc-verified.png',
10+
Declined: '/kyc-rejected.png',
11+
'In Review': '/kyc-pending.png',
12+
};
13+
14+
export const KYC_STATUS_ALT: Record<KycStatus, string> = {
15+
Approved: 'Verified',
16+
Declined: 'Rejected',
17+
'In Review': 'Pending',
18+
};
19+
20+
export function getKycImageAndAlt(
21+
status: KycStatus | null | undefined
22+
): { src: string; alt: string } | null {
23+
if (!status || !(status in KYC_STATUS_IMAGE)) return null;
24+
const kycStatus = status as KycStatus;
25+
return {
26+
src: KYC_STATUS_IMAGE[kycStatus],
27+
alt: KYC_STATUS_ALT[kycStatus],
28+
};
29+
}

public/kyc-pending.png

821 Bytes
Loading

public/kyc-rejected.png

803 Bytes
Loading

0 commit comments

Comments
 (0)