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.
|
2026-04-26 23:58:43 +02:00
|
|
|
|
* Tabs: approval status, documents, activity
|
2026-04-06 17:20:48 +02:00
|
|
|
|
*/
|
2026-07-14 21:23:45 +05:30
|
|
|
|
import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
|
2026-04-26 23:58:43 +02:00
|
|
|
|
import { ShieldCheck, FileText, Activity } from 'lucide-solid';
|
2026-04-06 17:20:48 +02:00
|
|
|
|
import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell';
|
2026-07-29 18:21:26 +02:00
|
|
|
|
import ProfilePage from '~/components/dashboard/ProfilePage';
|
|
|
|
|
|
import PortfolioPage from '~/components/dashboard/PortfolioPage';
|
2026-07-14 21:23:45 +05:30
|
|
|
|
import { getDocFields } from '~/lib/profile-fields-config';
|
2026-07-27 18:07:37 +05:30
|
|
|
|
import { presignDocumentUrl } from '~/lib/api';
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
const NAVY = '#0D0D2A';
|
|
|
|
|
|
const ORANGE = '#FF5E13';
|
|
|
|
|
|
|
|
|
|
|
|
type TabKey = 'approval_status' | 'documents' | 'activity';
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
fix(api): stop routing through a nonexistent /api/gateway rewrite layer
The K8s ingress for this domain routes any /api/* path directly to the
Rust gateway service — there is no rewrite/proxy layer in production that
collapses /api/gateway/* down to /api/*. That rewrite only exists as a
dev-only vite proxy, so lib/api.ts's request()/apiFetch() (used by Save
Changes, Submit for Verification, document upload, wallet, jobs,
marketplace, portfolio, and more) were 404ing on every call in production
with "Route not found in gateway". ProfilePage.tsx and
VerificationStatusPage.tsx each had their own local apiFetch with the same
assumption, doubly broken (paths already included /api/, prefixed again).
All three now hit the already-fully-qualified /api/... path directly,
matching how the working signup/login calls have always done it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 01:17:44 +05:30
|
|
|
|
// The K8s ingress routes /api/* directly to the Rust gateway service in
|
|
|
|
|
|
// production — there is no /api/gateway rewrite layer, so paths (already
|
|
|
|
|
|
// fully-qualified /api/... from callers) are hit as-is.
|
2026-04-06 17:20:48 +02:00
|
|
|
|
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") || ""
|
|
|
|
|
|
: "";
|
fix(api): stop routing through a nonexistent /api/gateway rewrite layer
The K8s ingress for this domain routes any /api/* path directly to the
Rust gateway service — there is no rewrite/proxy layer in production that
collapses /api/gateway/* down to /api/*. That rewrite only exists as a
dev-only vite proxy, so lib/api.ts's request()/apiFetch() (used by Save
Changes, Submit for Verification, document upload, wallet, jobs,
marketplace, portfolio, and more) were 404ing on every call in production
with "Route not found in gateway". ProfilePage.tsx and
VerificationStatusPage.tsx each had their own local apiFetch with the same
assumption, doubly broken (paths already included /api/, prefixed again).
All three now hit the already-fully-qualified /api/... path directly,
matching how the working signup/login calls have always done it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 01:17:44 +05:30
|
|
|
|
return fetch(path, {
|
2026-04-06 17:20:48 +02:00
|
|
|
|
...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.',
|
|
|
|
|
|
},
|
2026-07-29 15:14:37 +02:00
|
|
|
|
COMPLETED: {
|
|
|
|
|
|
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.',
|
|
|
|
|
|
},
|
2026-04-06 17:20:48 +02:00
|
|
|
|
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 = [
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{ key: 'submit', label: 'Submit Profile' },
|
|
|
|
|
|
{ key: 'review', label: 'Under Review' },
|
|
|
|
|
|
{ key: 'verify', label: 'Verified' },
|
2026-04-06 17:20:48 +02:00
|
|
|
|
{ key: 'approved', label: 'Approved' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function stepIndex(status: string): number {
|
|
|
|
|
|
switch (status) {
|
2026-04-26 23:58:43 +02:00
|
|
|
|
case 'NOT_SUBMITTED': return 0;
|
|
|
|
|
|
case 'PENDING': return 1;
|
|
|
|
|
|
case 'UNDER_REVIEW': return 2;
|
2026-04-06 17:20:48 +02:00
|
|
|
|
case 'DOCUMENTS_REQUESTED':
|
2026-04-26 23:58:43 +02:00
|
|
|
|
case 'REVISION_REQUESTED': return 2;
|
2026-07-29 15:14:37 +02:00
|
|
|
|
case 'COMPLETED':
|
2026-04-26 23:58:43 +02:00
|
|
|
|
case 'APPROVED': return 4;
|
|
|
|
|
|
default: return 1;
|
2026-04-06 17:20:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
roleKey: string;
|
|
|
|
|
|
onNavigate?: (sidebar: string) => void;
|
2026-04-26 23:58:43 +02:00
|
|
|
|
onVerificationStatusChange?: (status: string) => void;
|
2026-07-29 18:21:26 +02:00
|
|
|
|
runtimeFields?: string[];
|
|
|
|
|
|
runtimeTabs?: string[];
|
2026-04-06 17:20:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-19 16:35:30 +05:30
|
|
|
|
interface VerificationDocument {
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
value: any;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface VerificationStatusResponse {
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
reference_number: string;
|
|
|
|
|
|
document_request?: string | null;
|
|
|
|
|
|
rejection_reason?: string | null;
|
|
|
|
|
|
updated_at?: string | null;
|
|
|
|
|
|
activity_log?: Array<{ date: string; action: string; details: string }>;
|
|
|
|
|
|
documents: VerificationDocument[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Best-effort extraction of a viewable URL from a document's `value` payload,
|
|
|
|
|
|
// which may be a raw URL string or an upload-response object.
|
|
|
|
|
|
function docUrl(value: any): string | null {
|
|
|
|
|
|
if (!value) return null;
|
|
|
|
|
|
if (typeof value === 'string') return value;
|
|
|
|
|
|
if (typeof value === 'object') {
|
|
|
|
|
|
return value.url ?? value.file_url ?? value.fileUrl ?? value.path ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
|
export default function VerificationStatusPage(props: Props) {
|
2026-04-26 23:58:43 +02:00
|
|
|
|
const [activeTab, setActiveTab] = createSignal<TabKey>('approval_status');
|
2026-07-29 18:21:26 +02:00
|
|
|
|
const [inlineSection, setInlineSection] = createSignal<'profile' | 'portfolio'>('profile');
|
2026-04-06 17:20:48 +02:00
|
|
|
|
const [status, setStatus] = createSignal('NOT_SUBMITTED');
|
2026-07-19 16:35:30 +05:30
|
|
|
|
const [referenceNumber, setReferenceNumber] = createSignal<string | null>(null);
|
2026-04-06 17:20:48 +02:00
|
|
|
|
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('');
|
2026-04-26 23:58:43 +02:00
|
|
|
|
const [activityLog, setActivityLog] = createSignal<Array<{ date: string; action: string; details: string }>>([]);
|
2026-07-19 16:35:30 +05:30
|
|
|
|
const [documents, setDocuments] = createSignal<VerificationDocument[]>([]);
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await apiFetch(`/api/me/verification-status?roleKey=${props.roleKey}`);
|
|
|
|
|
|
if (res.ok) {
|
2026-07-19 16:35:30 +05:30
|
|
|
|
const d: VerificationStatusResponse = await res.json();
|
2026-04-26 23:58:43 +02:00
|
|
|
|
const nextStatus = String(d.status ?? 'NOT_SUBMITTED');
|
|
|
|
|
|
setStatus(nextStatus);
|
|
|
|
|
|
props.onVerificationStatusChange?.(nextStatus);
|
2026-07-19 16:35:30 +05:30
|
|
|
|
setReferenceNumber(d.reference_number ?? null);
|
2026-04-06 17:20:48 +02:00
|
|
|
|
setDocRequest(d.document_request ?? null);
|
|
|
|
|
|
setRejectionReason(d.rejection_reason ?? null);
|
|
|
|
|
|
setUpdatedAt(d.updated_at ?? null);
|
2026-04-26 23:58:43 +02:00
|
|
|
|
if (d.activity_log) {
|
|
|
|
|
|
setActivityLog(d.activity_log);
|
|
|
|
|
|
}
|
2026-07-19 16:35:30 +05:30
|
|
|
|
if (d.documents) {
|
|
|
|
|
|
setDocuments(d.documents);
|
|
|
|
|
|
}
|
2026-04-06 17:20:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const cfg = () => STATUS_CONFIG[status()] ?? STATUS_CONFIG.NOT_SUBMITTED;
|
|
|
|
|
|
const currentStep = () => stepIndex(status());
|
2026-07-14 21:23:45 +05:30
|
|
|
|
const docFields = createMemo(() => getDocFields(props.roleKey));
|
2026-04-06 17:20:48 +02:00
|
|
|
|
const canResubmit = () => ['DOCUMENTS_REQUESTED', 'REVISION_REQUESTED', 'REJECTED'].includes(status());
|
2026-07-29 18:21:26 +02:00
|
|
|
|
const isApproved = () => ['APPROVED', 'COMPLETED'].includes(status());
|
2026-07-30 15:36:29 +02:00
|
|
|
|
// Hide the inline form editors once submitted — PENDING/UNDER_REVIEW don't
|
|
|
|
|
|
// need editing, only statuses that require user action (NOT_SUBMITTED,
|
|
|
|
|
|
// DOCUMENTS_REQUESTED, REVISION_REQUESTED, REJECTED) do.
|
|
|
|
|
|
const showInlineEditors = () =>
|
|
|
|
|
|
['NOT_SUBMITTED', 'DOCUMENTS_REQUESTED', 'REVISION_REQUESTED', 'REJECTED'].includes(status());
|
2026-07-29 18:21:26 +02:00
|
|
|
|
const hasPortfolio = () => {
|
|
|
|
|
|
const role = String(props.roleKey || '').toUpperCase();
|
|
|
|
|
|
return role === 'JOB_SEEKER' || role !== 'COMPANY' && role !== 'CUSTOMER';
|
|
|
|
|
|
};
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
|
|
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');
|
2026-04-26 23:58:43 +02:00
|
|
|
|
props.onVerificationStatusChange?.('PENDING');
|
2026-04-06 17:20:48 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
const TABS: { key: TabKey; label: string; Icon: any }[] = [
|
|
|
|
|
|
{ key: 'approval_status', label: 'Approval Status', Icon: ShieldCheck },
|
|
|
|
|
|
{ key: 'documents', label: 'Documents', Icon: FileText },
|
|
|
|
|
|
{ key: 'activity', label: 'Activity', Icon: Activity },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-04-06 17:20:48 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ 'max-width': '640px' }}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: NAVY,
|
|
|
|
|
|
"border-radius": "12px",
|
|
|
|
|
|
padding: "16px 20px",
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
"align-items": "center",
|
|
|
|
|
|
gap: "12px",
|
|
|
|
|
|
"margin-bottom": "14px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span style={{ color: ORANGE }}>
|
|
|
|
|
|
<ShieldCheck size={24} />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: "0", "font-size": "18px", "font-weight": "800", color: "#fff" }}>
|
|
|
|
|
|
Verification Portal
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: "4px 0 0", "font-size": "13px", color: "rgba(255,255,255,0.65)" }}>
|
|
|
|
|
|
Track verification progress, documents, and updates.
|
|
|
|
|
|
</p>
|
2026-07-19 16:35:30 +05:30
|
|
|
|
<Show when={referenceNumber()}>
|
|
|
|
|
|
<p style={{ margin: "6px 0 0", "font-size": "13px", "font-family": "monospace", color: "rgba(255,255,255,0.85)" }}>
|
|
|
|
|
|
{referenceNumber()}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</Show>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tabs */}
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '0', 'border-bottom': '1px solid #E5E7EB', 'margin-bottom': '14px' }}>
|
|
|
|
|
|
<For each={TABS}>
|
|
|
|
|
|
{(tab) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setActiveTab(tab.key)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: "10px 16px",
|
|
|
|
|
|
"border-bottom": activeTab() === tab.key ? '2px solid #FF5E13' : '2px solid transparent',
|
|
|
|
|
|
background: "transparent",
|
|
|
|
|
|
color: activeTab() === tab.key ? "#FF5E13" : "#6B7280",
|
|
|
|
|
|
"font-size": "14px",
|
|
|
|
|
|
"font-weight": activeTab() === tab.key ? "700" : "500",
|
|
|
|
|
|
cursor: "pointer",
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
"align-items": "center",
|
|
|
|
|
|
gap: "6px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<tab.Icon size={16} />
|
|
|
|
|
|
{tab.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</For>
|
|
|
|
|
|
</div>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
|
|
|
|
|
<Show when={loading()}>
|
|
|
|
|
|
<div style={{ ...CARD, 'text-align': 'center', padding: '32px', color: '#9CA3AF' }}>
|
|
|
|
|
|
Loading verification status…
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={!loading()}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* ── Approval Status Tab ─────────────────────────────────────── */}
|
|
|
|
|
|
<Show when={activeTab() === 'approval_status'}>
|
|
|
|
|
|
{/* Main status card */}
|
2026-04-06 17:20:48 +02:00
|
|
|
|
<div style={{
|
|
|
|
|
|
...CARD,
|
2026-04-26 23:58:43 +02:00
|
|
|
|
background: cfg().bg,
|
|
|
|
|
|
border: `1px solid ${cfg().border}`,
|
2026-04-06 17:20:48 +02:00
|
|
|
|
'margin-bottom': '16px',
|
2026-04-26 23:58:43 +02:00
|
|
|
|
display: 'flex',
|
|
|
|
|
|
'flex-direction': 'column',
|
|
|
|
|
|
gap: '12px',
|
2026-04-06 17:20:48 +02:00
|
|
|
|
}}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<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}
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</p>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<Show when={updatedAt()}>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '11px', color: '#9CA3AF' }}>
|
|
|
|
|
|
Last updated: {new Date(updatedAt()!).toLocaleString('en-IN')}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* 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>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<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>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</div>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* Progress timeline */}
|
2026-07-29 15:14:37 +02:00
|
|
|
|
<Show when={!['APPROVED', 'COMPLETED'].includes(status())}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<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' }}>
|
|
|
|
|
|
<For each={FLOW_STEPS}>
|
2026-07-30 15:36:29 +02:00
|
|
|
|
{(step, idx) => (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<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: currentStep() > idx() ? '#FF5E13' : currentStep() === idx() + 1 ? '#FFF3EE' : '#F3F4F6',
|
|
|
|
|
|
color: currentStep() > idx() ? '#fff' : currentStep() === idx() + 1 ? '#FF5E13' : '#9CA3AF',
|
|
|
|
|
|
border: currentStep() === idx() + 1 ? '2px solid #FF5E13' : '2px solid transparent',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{currentStep() > idx() ? '✓' : idx() + 1}
|
2026-04-26 23:58:43 +02:00
|
|
|
|
</div>
|
2026-07-30 15:36:29 +02:00
|
|
|
|
<p style={{ margin: '4px 0 0', 'font-size': '10px', 'font-weight': '600', color: currentStep() > idx() || currentStep() === idx() + 1 ? '#374151' : '#9CA3AF', 'white-space': 'nowrap', 'text-align': 'center' }}>
|
|
|
|
|
|
{step.label}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Show when={idx() < FLOW_STEPS.length - 1}>
|
|
|
|
|
|
<div style={{ flex: '1', height: '2px', background: currentStep() > idx() ? '#FF5E13' : '#E5E7EB', 'margin-bottom': '18px' }} />
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2026-04-26 23:58:43 +02:00
|
|
|
|
</For>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</Show>
|
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* Actions */}
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '10px', 'flex-wrap': 'wrap' }}>
|
|
|
|
|
|
<Show when={status() === 'NOT_SUBMITTED'}>
|
2026-07-29 18:21:26 +02:00
|
|
|
|
<button type="button" onClick={() => setInlineSection('profile')} style={BTN_ORANGE}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
Fill My Profile
|
|
|
|
|
|
</button>
|
2026-07-29 18:21:26 +02:00
|
|
|
|
<button type="button" onClick={() => setInlineSection('portfolio')} style={BTN_GHOST}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
Fill My Portfolio
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={canResubmit()}>
|
2026-07-29 18:21:26 +02:00
|
|
|
|
<button type="button" onClick={() => setInlineSection('profile')} style={BTN_GHOST}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
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>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</Show>
|
|
|
|
|
|
|
2026-07-29 15:14:37 +02:00
|
|
|
|
<Show when={['APPROVED', 'COMPLETED'].includes(status())}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<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>
|
2026-07-29 18:21:26 +02:00
|
|
|
|
|
|
|
|
|
|
<Show when={showInlineEditors()}>
|
|
|
|
|
|
<div style={{ ...CARD, 'margin-top': '16px', padding: '16px' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'justify-content': 'space-between', 'align-items': 'center', gap: '12px', 'flex-wrap': 'wrap', 'margin-bottom': '14px' }}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '16px', 'font-weight': '800', color: '#111827' }}>
|
|
|
|
|
|
Complete Verification Details Here
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '6px 0 0', 'font-size': '13px', color: '#6B7280' }}>
|
|
|
|
|
|
Fill the required fields directly on this page, then submit for verification.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', 'flex-wrap': 'wrap' }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setInlineSection('profile')}
|
|
|
|
|
|
style={inlineSection() === 'profile' ? BTN_ORANGE : BTN_GHOST}
|
|
|
|
|
|
>
|
|
|
|
|
|
Profile Fields
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<Show when={hasPortfolio()}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => setInlineSection('portfolio')}
|
|
|
|
|
|
style={inlineSection() === 'portfolio' ? BTN_ORANGE : BTN_GHOST}
|
|
|
|
|
|
>
|
|
|
|
|
|
Portfolio Fields
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={inlineSection() === 'profile'}>
|
|
|
|
|
|
<ProfilePage
|
|
|
|
|
|
roleKey={props.roleKey}
|
|
|
|
|
|
runtimeFields={props.runtimeFields || []}
|
2026-07-30 15:36:29 +02:00
|
|
|
|
onVerificationStatusChange={(s) => {
|
|
|
|
|
|
setStatus(s);
|
|
|
|
|
|
props.onVerificationStatusChange?.(s);
|
|
|
|
|
|
}}
|
2026-07-29 18:21:26 +02:00
|
|
|
|
onNavigate={props.onNavigate}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
|
|
<Show when={inlineSection() === 'portfolio' && hasPortfolio()}>
|
|
|
|
|
|
<PortfolioPage
|
|
|
|
|
|
roleKey={props.roleKey}
|
|
|
|
|
|
runtimeTabs={props.runtimeTabs || []}
|
|
|
|
|
|
runtimeFields={props.runtimeFields || []}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</Show>
|
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* ── Documents Tab ───────────────────────────────────────────── */}
|
|
|
|
|
|
<Show when={activeTab() === 'documents'}>
|
|
|
|
|
|
<div style={CARD}>
|
|
|
|
|
|
<p style={{ margin: '0 0 14px', 'font-size': '16px', 'font-weight': '700', color: '#111827' }}>
|
|
|
|
|
|
Submitted Documents
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</p>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<Show when={!docRequest() && !rejectionReason() && status() === 'NOT_SUBMITTED'}>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', color: '#6B7280' }}>
|
|
|
|
|
|
No documents submitted yet. Complete your profile to submit documents for verification.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={docRequest()}>
|
|
|
|
|
|
<div style={{ background: '#FFF7ED', border: '1px solid #FED7AA', 'border-radius': '10px', padding: '14px', 'margin-bottom': '12px' }}>
|
|
|
|
|
|
<p style={{ margin: '0 0 6px', 'font-size': '13px', 'font-weight': '700', color: '#C2410C' }}>
|
|
|
|
|
|
Document Request
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', color: '#374151' }}>{docRequest()}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={rejectionReason()}>
|
|
|
|
|
|
<div style={{ background: '#FEF2F2', border: '1px solid #FECACA', 'border-radius': '10px', padding: '14px', 'margin-bottom': '12px' }}>
|
|
|
|
|
|
<p style={{ margin: '0 0 6px', 'font-size': '13px', 'font-weight': '700', color: '#B91C1C' }}>
|
|
|
|
|
|
Rejection Reason
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', color: '#374151' }}>{rejectionReason()}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={status() !== 'NOT_SUBMITTED'}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
|
2026-07-19 16:35:30 +05:30
|
|
|
|
<For each={documents().length > 0 ? documents() : docFields().map((f) => ({ type: f.key, value: null, status: status() }))}>
|
|
|
|
|
|
{(doc) => {
|
|
|
|
|
|
const fieldLabel = () => docFields().find((f) => f.key === doc.type)?.label ?? doc.type;
|
|
|
|
|
|
const url = () => docUrl(doc.value);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ border: '1px solid #E5E7EB', 'border-radius': '10px', padding: '12px', background: '#FCFCFD' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'justify-content': 'space-between', 'align-items': 'center', gap: '10px', 'flex-wrap': 'wrap' }}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#111827' }}>{fieldLabel()}</p>
|
|
|
|
|
|
<Show
|
|
|
|
|
|
when={url()}
|
|
|
|
|
|
fallback={
|
|
|
|
|
|
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>No file uploaded</p>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
2026-07-27 18:07:37 +05:30
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={async () => {
|
|
|
|
|
|
const signed = await presignDocumentUrl(url()!);
|
|
|
|
|
|
window.open(signed, '_blank', 'noopener,noreferrer');
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
margin: '4px 0 0', 'font-size': '12px', color: '#FF5E13',
|
|
|
|
|
|
'text-decoration': 'underline', display: 'inline-block',
|
|
|
|
|
|
background: 'none', border: 'none', padding: '0', cursor: 'pointer',
|
|
|
|
|
|
}}
|
2026-07-19 16:35:30 +05:30
|
|
|
|
>
|
|
|
|
|
|
View document
|
2026-07-27 18:07:37 +05:30
|
|
|
|
</button>
|
2026-07-19 16:35:30 +05:30
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p style={{
|
|
|
|
|
|
margin: '0',
|
|
|
|
|
|
'font-size': '12px',
|
|
|
|
|
|
'font-weight': '600',
|
|
|
|
|
|
color: doc.status === 'APPROVED' ? '#059669' : doc.status === 'REJECTED' ? '#DC2626' : '#6B7280',
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{doc.status === 'APPROVED' ? '✓ Verified' : doc.status === 'REJECTED' ? '✕ Rejected' : doc.status === 'SUBMITTED' ? '◌ Submitted' : '◌ Pending review'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}}
|
2026-07-14 21:23:45 +05:30
|
|
|
|
</For>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
2026-04-26 23:58:43 +02:00
|
|
|
|
{/* ── Activity Tab ────────────────────────────────────────────── */}
|
|
|
|
|
|
<Show when={activeTab() === 'activity'}>
|
|
|
|
|
|
<div style={CARD}>
|
|
|
|
|
|
<p style={{ margin: '0 0 14px', 'font-size': '16px', 'font-weight': '700', color: '#111827' }}>
|
|
|
|
|
|
Verification Activity
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<Show when={activityLog().length === 0}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '10px' }}>
|
|
|
|
|
|
<Show when={status() === 'NOT_SUBMITTED'}>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}>
|
|
|
|
|
|
<div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: '#E5E7EB', 'margin-top': '6px', 'flex-shrink': '0' }} />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#374151' }}>Profile created</p>
|
|
|
|
|
|
<p style={{ margin: '2px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>{updatedAt() ? new Date(updatedAt()!).toLocaleString('en-IN') : 'Just now'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={!['NOT_SUBMITTED'].includes(status())}>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}>
|
|
|
|
|
|
<div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: '#FDE68A', 'margin-top': '6px', 'flex-shrink': '0' }} />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#374151' }}>Profile submitted for verification</p>
|
|
|
|
|
|
<p style={{ margin: '2px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>{updatedAt() ? new Date(updatedAt()!).toLocaleString('en-IN') : 'Recently'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
2026-07-29 15:14:37 +02:00
|
|
|
|
<Show when={['APPROVED', 'COMPLETED'].includes(status())}>
|
2026-04-26 23:58:43 +02:00
|
|
|
|
<div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}>
|
|
|
|
|
|
<div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: '#10B981', 'margin-top': '6px', 'flex-shrink': '0' }} />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#374151' }}>Profile approved</p>
|
|
|
|
|
|
<p style={{ margin: '2px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>{updatedAt() ? new Date(updatedAt()!).toLocaleString('en-IN') : 'Recently'}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
<Show when={activityLog().length > 0}>
|
|
|
|
|
|
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
|
|
|
|
|
|
<For each={activityLog()}>
|
|
|
|
|
|
{(item) => (
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', 'align-items': 'flex-start' }}>
|
|
|
|
|
|
<div style={{ width: '8px', height: '8px', 'border-radius': '50%', background: ORANGE, 'margin-top': '6px', 'flex-shrink': '0' }} />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p style={{ margin: '0', 'font-size': '13px', 'font-weight': '600', color: '#374151' }}>{item.action}</p>
|
|
|
|
|
|
<p style={{ margin: '2px 0 0', 'font-size': '12px', color: '#9CA3AF' }}>{item.date}</p>
|
|
|
|
|
|
<Show when={item.details}>
|
|
|
|
|
|
<p style={{ margin: '4px 0 0', 'font-size': '12px', color: '#6B7280' }}>{item.details}</p>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</For>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Show>
|
2026-04-06 17:20:48 +02:00
|
|
|
|
</Show>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|