2026-04-06 17:20:48 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* VerificationStatusPage — shows the user their current verification state.
|
|
|
|
|
|
* Handles: NOT_SUBMITTED, PENDING, UNDER_REVIEW, DOCUMENTS_REQUESTED,
|
|
|
|
|
|
* REVISION_REQUESTED, APPROVED, REJECTED.
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { Show, createSignal, onMount } from 'solid-js';
|
|
|
|
|
|
import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell';
|
|
|
|
|
|
|
|
|
|
|
|
const API = '/api/gateway';
|
|
|
|
|
|
|
|
|
|
|
|
async function apiFetch(path: string, opts?: RequestInit) {
|
2026-04-22 01:32:43 +02:00
|
|
|
|
const token =
|
|
|
|
|
|
typeof window !== "undefined"
|
|
|
|
|
|
? window.sessionStorage.getItem("nxtgauge_access_token") || ""
|
|
|
|
|
|
: "";
|
2026-04-06 17:20:48 +02:00
|
|
|
|
return fetch(`${API}${path}`, {
|
|
|
|
|
|
...opts,
|
2026-04-22 01:32:43 +02:00
|
|
|
|
credentials: "include",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
|
|
|
|
...(opts?.headers ?? {}),
|
|
|
|
|
|
},
|
2026-04-06 17:20:48 +02:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Status config ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
const STATUS_CONFIG: Record<string, {
|
|
|
|
|
|
emoji: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
color: string;
|
|
|
|
|
|
bg: string;
|
|
|
|
|
|
border: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
}> = {
|
|
|
|
|
|
NOT_SUBMITTED: {
|
|
|
|
|
|
emoji: '📋',
|
|
|
|
|
|
label: 'Not Submitted',
|
|
|
|
|
|
color: '#6B7280',
|
|
|
|
|
|
bg: '#F9FAFB',
|
|
|
|
|
|
border: '#E5E7EB',
|
|
|
|
|
|
description: 'Complete your My Profile and My Portfolio, then submit for verification to start using the platform.',
|
|
|
|
|
|
},
|
|
|
|
|
|
PENDING: {
|
|
|
|
|
|
emoji: '⏳',
|
|
|
|
|
|
label: 'Pending Review',
|
|
|
|
|
|
color: '#92400E',
|
|
|
|
|
|
bg: '#FFFBEB',
|
|
|
|
|
|
border: '#FDE68A',
|
|
|
|
|
|
description: 'Your profile has been submitted and is in the review queue. We typically respond within 24–48 hours.',
|
|
|
|
|
|
},
|
|
|
|
|
|
UNDER_REVIEW: {
|
|
|
|
|
|
emoji: '🔍',
|
|
|
|
|
|
label: 'Under Review',
|
|
|
|
|
|
color: '#1E40AF',
|
|
|
|
|
|
bg: '#EEF2FF',
|
|
|
|
|
|
border: '#BFDBFE',
|
|
|
|
|
|
description: 'Our team is actively reviewing your submission. You will be notified once a decision is made.',
|
|
|
|
|
|
},
|
|
|
|
|
|
DOCUMENTS_REQUESTED: {
|
|
|
|
|
|
emoji: '📎',
|
|
|
|
|
|
label: 'Documents Requested',
|
|
|
|
|
|
color: '#C2410C',
|
|
|
|
|
|
bg: '#FFF7ED',
|
|
|
|
|
|
border: '#FED7AA',
|
|
|
|
|
|
description: 'Admin has requested additional or clearer documents. Please review the message below and resubmit.',
|
|
|
|
|
|
},
|
|
|
|
|
|
REVISION_REQUESTED: {
|
|
|
|
|
|
emoji: '✏️',
|
|
|
|
|
|
label: 'Revision Requested',
|
|
|
|
|
|
color: '#C2410C',
|
|
|
|
|
|
bg: '#FFF7ED',
|
|
|
|
|
|
border: '#FED7AA',
|
|
|
|
|
|
description: 'Admin has requested changes to your profile information. Please update and resubmit.',
|
|
|
|
|
|
},
|
|
|
|
|
|
APPROVED: {
|
|
|
|
|
|
emoji: '✅',
|
|
|
|
|
|
label: 'Approved',
|
|
|
|
|
|
color: '#065F46',
|
|
|
|
|
|
bg: '#ECFDF5',
|
|
|
|
|
|
border: '#6EE7B7',
|
|
|
|
|
|
description: 'Your profile has been verified and approved. You now have full access to the platform.',
|
|
|
|
|
|
},
|
|
|
|
|
|
REJECTED: {
|
|
|
|
|
|
emoji: '❌',
|
|
|
|
|
|
label: 'Rejected',
|
|
|
|
|
|
color: '#991B1B',
|
|
|
|
|
|
bg: '#FEF2F2',
|
|
|
|
|
|
border: '#FECACA',
|
|
|
|
|
|
description: 'Your verification was rejected. Please review the reason below, update your profile, and resubmit.',
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const FLOW_STEPS = [
|
|
|
|
|
|
{ key: 'submit', label: 'Submit Profile' },
|
|
|
|
|
|
{ key: 'review', label: 'Under Review' },
|
|
|
|
|
|
{ key: 'verify', label: 'Verified' },
|
|
|
|
|
|
{ key: 'approved', label: 'Approved' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function stepIndex(status: string): number {
|
|
|
|
|
|
switch (status) {
|
|
|
|
|
|
case 'NOT_SUBMITTED': return 0;
|
|
|
|
|
|
case 'PENDING': return 1;
|
|
|
|
|
|
case 'UNDER_REVIEW': return 2;
|
|
|
|
|
|
case 'DOCUMENTS_REQUESTED':
|
|
|
|
|
|
case 'REVISION_REQUESTED': return 2; // still at review stage
|
|
|
|
|
|
case 'APPROVED': return 4;
|
|
|
|
|
|
default: return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
roleKey: string;
|
|
|
|
|
|
onNavigate?: (sidebar: string) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function VerificationStatusPage(props: Props) {
|
|
|
|
|
|
const [status, setStatus] = createSignal('NOT_SUBMITTED');
|
|
|
|
|
|
const [docRequest, setDocRequest] = createSignal<string | null>(null);
|
|
|
|
|
|
const [rejectionReason, setRejectionReason] = createSignal<string | null>(null);
|
|
|
|
|
|
const [updatedAt, setUpdatedAt] = createSignal<string | null>(null);
|
|
|
|
|
|
const [loading, setLoading] = createSignal(true);
|
|
|
|
|
|
const [resubmitting, setResubmitting] = createSignal(false);
|
|
|
|
|
|
const [resubmitMsg, setResubmitMsg] = createSignal('');
|
|
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await apiFetch(`/api/me/verification-status?roleKey=${props.roleKey}`);
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
|
const d = await res.json();
|
|
|
|
|
|
setStatus(d.status ?? 'NOT_SUBMITTED');
|
|
|
|
|
|
setDocRequest(d.document_request ?? null);
|
|
|
|
|
|
setRejectionReason(d.rejection_reason ?? null);
|
|
|
|
|
|
setUpdatedAt(d.updated_at ?? null);
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const cfg = () => STATUS_CONFIG[status()] ?? STATUS_CONFIG.NOT_SUBMITTED;
|
|
|
|
|
|
const currentStep = () => stepIndex(status());
|
|
|
|
|
|
const canResubmit = () => ['DOCUMENTS_REQUESTED', 'REVISION_REQUESTED', 'REJECTED'].includes(status());
|
|
|
|
|
|
|
|
|
|
|
|
const handleResubmit = async () => {
|
|
|
|
|
|
setResubmitting(true);
|
|
|
|
|
|
setResubmitMsg('');
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await apiFetch('/api/profile/submit-for-verification', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: JSON.stringify({ roleKey: props.roleKey }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const d = await res.json().catch(() => ({}));
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
|
setStatus('PENDING');
|
|
|
|
|
|
setDocRequest(null);
|
|
|
|
|
|
setRejectionReason(null);
|
|
|
|
|
|
setResubmitMsg('Resubmitted successfully! We will review your profile.');
|
|
|
|
|
|
} else if (res.status === 409) {
|
|
|
|
|
|
setResubmitMsg(d.error ?? 'A verification is already in progress.');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setResubmitMsg(d.error ?? 'Resubmission failed. Please try again.');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setResubmitMsg('Network error. Please try again.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setResubmitting(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ 'max-width': '640px' }}>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={loading()}>
|
|
|
|
|
|
<div style={{ ...CARD, 'text-align': 'center', padding: '32px', color: '#9CA3AF' }}>
|
|
|
|
|
|
Loading verification status…
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={!loading()}>
|
|
|
|
|
|
|
|
|
|
|
|
{/* ── Main status card ─────────────────────────────────────────── */}
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
...CARD,
|
|
|
|
|
|
background: cfg().bg,
|
|
|
|
|
|
border: `1px solid ${cfg().border}`,
|
|
|
|
|
|
'margin-bottom': '16px',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
'flex-direction': 'column',
|
|
|
|
|
|
gap: '12px',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'align-items': 'center', gap: '12px' }}>
|
|
|
|
|
|
<span style={{ 'font-size': '36px', 'line-height': '1' }}>{cfg().emoji}</span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '11px', 'text-transform': 'uppercase', 'letter-spacing': '0.08em', color: cfg().color, 'font-weight': '700' }}>
|
|
|
|
|
|
Verification Status
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '2px 0 0', 'font-size': '22px', 'font-weight': '800', color: cfg().color }}>
|
|
|
|
|
|
{cfg().label}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', color: '#374151', 'line-height': '1.6' }}>
|
|
|
|
|
|
{cfg().description}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={updatedAt()}>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '11px', color: '#9CA3AF' }}>
|
|
|
|
|
|
Last updated: {new Date(updatedAt()!).toLocaleString('en-IN')}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* ── Doc request / rejection reason ──────────────────────────── */}
|
|
|
|
|
|
<Show when={docRequest()}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
...CARD,
|
|
|
|
|
|
background: '#FFF7ED',
|
|
|
|
|
|
border: '1px solid #FED7AA',
|
|
|
|
|
|
'margin-bottom': '16px',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<p style={{ margin: '0 0 6px', 'font-size': '12px', 'font-weight': '700', 'text-transform': 'uppercase', 'letter-spacing': '0.06em', color: '#C2410C' }}>
|
|
|
|
|
|
Document Request from Admin
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '14px', color: '#374151', 'line-height': '1.6' }}>
|
|
|
|
|
|
{docRequest()}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={rejectionReason()}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
...CARD,
|
|
|
|
|
|
background: '#FEF2F2',
|
|
|
|
|
|
border: '1px solid #FECACA',
|
|
|
|
|
|
'margin-bottom': '16px',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<p style={{ margin: '0 0 6px', 'font-size': '12px', 'font-weight': '700', 'text-transform': 'uppercase', 'letter-spacing': '0.06em', color: '#B91C1C' }}>
|
|
|
|
|
|
Rejection Reason
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '14px', color: '#374151', 'line-height': '1.6' }}>
|
|
|
|
|
|
{rejectionReason()}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
{/* ── Progress timeline ──────────────────────────────────────── */}
|
|
|
|
|
|
<Show when={status() !== 'APPROVED'}>
|
|
|
|
|
|
<div style={{ ...CARD, 'margin-bottom': '16px' }}>
|
|
|
|
|
|
<p style={{ margin: '0 0 14px', 'font-size': '14px', 'font-weight': '700', color: '#111827' }}>
|
|
|
|
|
|
Verification Progress
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div style={{ display: 'flex', 'align-items': 'center', gap: '0' }}>
|
|
|
|
|
|
{FLOW_STEPS.map((step, idx) => {
|
|
|
|
|
|
const done = currentStep() > idx;
|
|
|
|
|
|
const active = currentStep() === idx + 1;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div style={{ display: 'flex', 'flex-direction': 'column', 'align-items': 'center', 'flex-shrink': '0' }}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
width: '28px',
|
|
|
|
|
|
height: '28px',
|
|
|
|
|
|
'border-radius': '999px',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
'align-items': 'center',
|
|
|
|
|
|
'justify-content': 'center',
|
|
|
|
|
|
'font-size': '11px',
|
|
|
|
|
|
'font-weight': '800',
|
|
|
|
|
|
background: done ? '#FF5E13' : active ? '#FFF3EE' : '#F3F4F6',
|
|
|
|
|
|
color: done ? '#fff' : active ? '#FF5E13' : '#9CA3AF',
|
|
|
|
|
|
border: active ? '2px solid #FF5E13' : '2px solid transparent',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{done ? '✓' : idx + 1}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p style={{
|
|
|
|
|
|
margin: '4px 0 0',
|
|
|
|
|
|
'font-size': '10px',
|
|
|
|
|
|
'font-weight': '600',
|
|
|
|
|
|
color: done || active ? '#374151' : '#9CA3AF',
|
|
|
|
|
|
'white-space': 'nowrap',
|
|
|
|
|
|
'text-align': 'center',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{step.label}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{idx < FLOW_STEPS.length - 1 && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
flex: '1',
|
|
|
|
|
|
height: '2px',
|
|
|
|
|
|
background: done ? '#FF5E13' : '#E5E7EB',
|
|
|
|
|
|
'margin-bottom': '18px',
|
|
|
|
|
|
}} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
{/* ── Actions ───────────────────────────────────────────────── */}
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '10px', 'flex-wrap': 'wrap' }}>
|
|
|
|
|
|
<Show when={status() === 'NOT_SUBMITTED'}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => props.onNavigate?.('My Profile')}
|
|
|
|
|
|
style={BTN_ORANGE}
|
|
|
|
|
|
>
|
|
|
|
|
|
Fill My Profile
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => props.onNavigate?.('My Portfolio')}
|
|
|
|
|
|
style={BTN_GHOST}
|
|
|
|
|
|
>
|
|
|
|
|
|
Fill My Portfolio
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={canResubmit()}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => props.onNavigate?.('My Profile')}
|
|
|
|
|
|
style={BTN_GHOST}
|
|
|
|
|
|
>
|
|
|
|
|
|
Update My Profile
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={handleResubmit}
|
|
|
|
|
|
disabled={resubmitting()}
|
|
|
|
|
|
style={{ ...BTN_ORANGE, opacity: resubmitting() ? '0.7' : '1' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
{resubmitting() ? 'Resubmitting…' : 'Resubmit for Verification'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={resubmitMsg()}>
|
|
|
|
|
|
<p style={{
|
|
|
|
|
|
margin: '12px 0 0',
|
|
|
|
|
|
'font-size': '13px',
|
|
|
|
|
|
'font-weight': '600',
|
|
|
|
|
|
color: resubmitMsg().includes('successfully') ? '#10B981' : '#EF4444',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{resubmitMsg()}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
{/* ── Approved state: success message ───────────────────────── */}
|
|
|
|
|
|
<Show when={status() === 'APPROVED'}>
|
|
|
|
|
|
<div style={{ ...CARD, background: '#ECFDF5', border: '1px solid #6EE7B7', 'text-align': 'center', padding: '32px' }}>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '48px' }}>🎉</p>
|
|
|
|
|
|
<p style={{ margin: '12px 0 4px', 'font-size': '20px', 'font-weight': '800', color: '#065F46' }}>
|
|
|
|
|
|
You're Verified!
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '14px', color: '#047857', 'line-height': '1.6' }}>
|
|
|
|
|
|
Your profile is approved. Start exploring opportunities on Nxtgauge.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|