From 8b97ed2d904ad70a7e1e87e6bcfe0ccd7e70e58c Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Wed, 27 May 2026 11:25:12 +0530 Subject: [PATCH 1/3] add /dev/kyc-ui audit page for verification vs capabilities review dev-only page showing all KYC UI states, modals, copy audit, and a proposed "Where you can pay" page design that separates identity confirmation from per-region payment status. splits manteca into separate argentina/brazil entries matching the geo-scoped enrollment. --- src/app/dev/kyc-ui/page.tsx | 603 ++++++++++++++++++++++++++++++++++++ 1 file changed, 603 insertions(+) create mode 100644 src/app/dev/kyc-ui/page.tsx diff --git a/src/app/dev/kyc-ui/page.tsx b/src/app/dev/kyc-ui/page.tsx new file mode 100644 index 000000000..021193cfd --- /dev/null +++ b/src/app/dev/kyc-ui/page.tsx @@ -0,0 +1,603 @@ +'use client' + +import { useState } from 'react' +import NavHeader from '@/components/Global/NavHeader' +import { Button } from '@/components/0_Bruddle/Button' +import Card from '@/components/Global/Card' +import InfoCard from '@/components/Global/InfoCard' +import { Icon } from '@/components/Global/Icons/Icon' +import StatusBadge from '@/components/Global/Badges/StatusBadge' +import { ActionListCard } from '@/components/ActionListCard' +import { getCardPosition } from '@/components/Global/Card/card.utils' + +// kyc state components (rendered inline with mock data) +import { KycCompleted } from '@/components/Kyc/states/KycCompleted' +import { KycProcessing } from '@/components/Kyc/states/KycProcessing' +import { KycActionRequired } from '@/components/Kyc/states/KycActionRequired' +import { KycRequiresDocuments } from '@/components/Kyc/states/KycRequiresDocuments' +import { KycProviderRejection } from '@/components/Kyc/states/KycProviderRejection' +import { KycFailed } from '@/components/Kyc/states/KycFailed' +import { KycNotStarted } from '@/components/Kyc/states/KycNotStarted' + +// modals +import { InitiateKycModal } from '@/components/Kyc/InitiateKycModal' +import { GuestVerificationModal } from '@/components/Global/GuestVerificationModal' + +// types +import type { ProviderRejectionInfo } from '@/hooks/useProviderRejectionStatus' + +// ─── mock data ────────────────────────────────────────────────── + +const MOCK_FIXABLE_REJECTION: ProviderRejectionInfo = { + provider: 'BRIDGE', + state: 'fixable', + userMessage: 'We need an additional document to enable payments.', + rejectedRails: [], + kycVerification: null, + selfHealAttempt: 1, + maxAttempts: 3, +} + +const MOCK_BLOCKED_REJECTION: ProviderRejectionInfo = { + provider: 'BRIDGE', + state: 'blocked', + userMessage: "We couldn't enable payments for your account. Please contact support for assistance.", + rejectedRails: [], + kycVerification: null, + selfHealAttempt: 3, + maxAttempts: 3, +} + +const MOCK_TOS_REJECTION: ProviderRejectionInfo = { + provider: 'BRIDGE', + state: 'fixable', + userMessage: 'Please accept the terms to enable payments.', + rejectedRails: [], + kycVerification: null, + selfHealAttempt: 0, + maxAttempts: 3, +} + +const MOCK_FIELDS_REJECTION: ProviderRejectionInfo = { + provider: 'BRIDGE', + state: 'fixable', + userMessage: 'We need a few more details to enable payments.', + rejectedRails: [], + kycVerification: null, + selfHealAttempt: 0, + maxAttempts: 3, +} + +// ─── helpers ──────────────────────────────────────────────────── + +function StateCard({ + label, + scenario, + children, + problem, +}: { + label: string + scenario: string + children: React.ReactNode + problem?: string +}) { + return ( +
+
+
+

{label}

+

{scenario}

+
+ {problem && } +
+ + {children} + + {problem && ( +

{problem}

+ )} +
+ ) +} + +function CopyAuditRow({ + file, + current, + proposed, + severity, +}: { + file: string + current: string + proposed: string + severity: 'HIGH' | 'MED' | 'LOW' +}) { + const badgeStatus = severity === 'HIGH' ? 'failed' : severity === 'MED' ? 'pending' : 'processing' + return ( + +
+ + {file} +
+
+

{current}

+

{proposed}

+
+
+ ) +} + +// ─── proposed regions page mockup ─────────────────────────────── + +function ProposedRegionsPage({ userState }: { userState: 'new' | 'verified-rfi' | 'eea' | 'happy' }) { + const noop = () => {} + + // bridge rails — europe, us, mexico + const europePayments = [ + { name: 'SEPA bank transfer', desc: '30+ countries' }, + { name: 'UK bank transfer', desc: 'Faster Payments' }, + ] + const usPayments = [ + { name: 'US bank transfer', desc: 'ACH' }, + { name: 'US wire transfer', desc: 'Wire' }, + { name: 'Mexico SPEI', desc: 'Instant' }, + ] + + // manteca rails — separate enrollment per country + const argPayments = [ + { name: 'MercadoPago QR', desc: 'Pay at stores' }, + { name: 'Bank transfer', desc: 'Send to your own accounts' }, + ] + const brPayments = [ + { name: 'PIX QR', desc: 'Pay at stores and send to friends' }, + { name: 'Bank transfer', desc: 'Send to your own accounts' }, + ] + + return ( +
+ + + {/* id confirmed — subtle inline, not a banner */} + {userState !== 'new' && ( +
+ + ID confirmed +
+ )} + + {/* new user: onboarding */} + {userState === 'new' && ( + +
+
+ +
+
+

Send money worldwide

+

+ Bank transfers, QR payments, and more. Confirm your ID to get started. +

+
+ +
+
+ )} + + {/* eea deadline notice */} + {userState === 'eea' && ( + + )} + + {/* active payment methods */} + {userState !== 'new' && ( + <> + {/* europe (bridge) */} +
+

Europe

+ {europePayments.map((p, i) => { + const needsAction = userState === 'eea' && p.name === 'SEPA bank transfer' + return ( + : undefined} + title={p.name} + description={needsAction ? 'Needs a quick update to stay active' : p.desc} + descriptionClassName="text-xs" + onClick={noop} + rightContent={ + needsAction ? ( + + ) : ( + + ) + } + /> + ) + })} +
+ + {/* us & mexico (bridge) */} +
+

US & Mexico

+ {usPayments.map((p, i) => { + const needsAction = userState === 'verified-rfi' && p.name === 'US bank transfer' + return ( + : undefined} + title={p.name} + description={needsAction ? 'One document needed to start sending' : p.desc} + descriptionClassName="text-xs" + onClick={noop} + rightContent={ + needsAction ? ( + + ) : ( + + ) + } + /> + ) + })} +
+ + {/* argentina (manteca AR) — separate enrollment */} +
+

Argentina

+ {argPayments.map((p, i) => ( + } + /> + ))} +
+ + {/* brazil (manteca BR) — separate enrollment */} +
+

Brazil

+ {brPayments.map((p, i) => ( + } + /> + ))} +
+ + )} + + {/* new user: preview what they can unlock */} + {userState === 'new' && ( +
+

Available after setup

+ {[ + { name: 'Europe', desc: 'SEPA and UK bank transfers to 30+ countries' }, + { name: 'US & Mexico', desc: 'ACH, wire transfers, Mexico SPEI' }, + { name: 'Argentina', desc: 'MercadoPago QR + bank transfers' }, + { name: 'Brazil', desc: 'PIX QR + bank transfers' }, + ].map((r, i, arr) => ( + } + title={r.name} + description={r.desc} + descriptionClassName="text-xs" + onClick={() => {}} + isDisabled + rightContent={} + /> + ))} +
+ )} +
+ ) +} + +// ─── page ─────────────────────────────────────────────────────── + +export default function KycUiAuditPage() { + const [openModal, setOpenModal] = useState(null) + const [activeTab, setActiveTab] = useState<'proposed' | 'states' | 'modals' | 'audit'>('proposed') + const noop = () => {} + + return ( +
+ + + {/* tab nav */} +
+ {(['proposed', 'states', 'modals', 'audit'] as const).map((tab) => ( + + ))} +
+ + {/* ─── TAB: proposed page ─────────────────────────── */} + {activeTab === 'proposed' && ( +
+ + +
+

Brand new user

+

hasn't confirmed ID yet

+ + + +
+ +
+

One payment method needs a document

+

everything works except US bank transfers — needs proof of address

+ + + +
+ +
+

European user with deadline

+

SEPA needs a small update by June 29 to stay active

+ + + +
+ +
+

Everything working

+

all set up, no actions needed

+ + + +
+
+ )} + + {/* ─── TAB: current drawer states ─────────────────── */} + {activeTab === 'states' && ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ )} + + {/* ─── TAB: modals ────────────────────────────────── */} + {activeTab === 'modals' && ( +
+
+

InitiateKycModal

+
+ {[ + { key: 'default', label: 'Fresh KYC' }, + { key: 'provider_rejection', label: 'Needs docs' }, + { key: 'blocked', label: 'Blocked' }, + { key: 'cross_region', label: 'Cross region' }, + { key: 'error', label: 'Error' }, + ].map(({ key, label }) => ( + + ))} +
+
+ + + +
+

Guest modal

+ +
+ + {/* render modals */} + setOpenModal(null)} onVerify={noop} variant="default" /> + setOpenModal(null)} onVerify={noop} variant="provider_rejection" providerMessage="Please upload a clearer photo of your ID to continue." /> + setOpenModal(null)} onVerify={noop} variant="blocked" /> + setOpenModal(null)} onVerify={noop} variant="cross_region" regionName="Brazil" /> + setOpenModal(null)} onVerify={noop} error="Bridge API returned an unexpected error." /> + setOpenModal(null)} description="You need to verify your identity to use bank transfers." secondaryCtaLabel="Continue as guest" /> +
+ )} + + {/* ─── TAB: copy audit ────────────────────────────── */} + {activeTab === 'audit' && ( +
+
+

Copy issues

+

+ The app tells users they're verified, then asks them to "complete verification" for a specific payment method. +

+ + + + + + + + + + + + + +
+ +
+

Principles

+ +
+ +
+

Good example from today's code

+ +

+ "Your identity is already verified. To send money in this region, we need a valid ID from there." +

+

+ cross_region variant — acknowledges the user, explains what's needed, ties it to their goal +

+
+ +
+
+ )} +
+ ) +} From 9a8dc7bd7b00b62ac85d39bb78f7c50d386003de Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Wed, 27 May 2026 11:30:48 +0530 Subject: [PATCH 2/3] =?UTF-8?q?remove=20useless=20'ID=20confirmed'=20banne?= =?UTF-8?q?r=20=E2=80=94=20users=20don't=20need=20a=20status=20label=20whe?= =?UTF-8?q?n=20everything=20works?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/dev/kyc-ui/page.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/dev/kyc-ui/page.tsx b/src/app/dev/kyc-ui/page.tsx index 021193cfd..9a11c55e4 100644 --- a/src/app/dev/kyc-ui/page.tsx +++ b/src/app/dev/kyc-ui/page.tsx @@ -156,14 +156,6 @@ function ProposedRegionsPage({ userState }: { userState: 'new' | 'verified-rfi'
- {/* id confirmed — subtle inline, not a banner */} - {userState !== 'new' && ( -
- - ID confirmed -
- )} - {/* new user: onboarding */} {userState === 'new' && ( From c0997d01525452b36986fbb9da0a67722eeb579e Mon Sep 17 00:00:00 2001 From: kushagrasarathe <76868364+kushagrasarathe@users.noreply.github.com> Date: Wed, 27 May 2026 11:32:14 +0530 Subject: [PATCH 3/3] remove wrapping cards from proposed page sections --- src/app/dev/kyc-ui/page.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/app/dev/kyc-ui/page.tsx b/src/app/dev/kyc-ui/page.tsx index 9a11c55e4..6d73535ac 100644 --- a/src/app/dev/kyc-ui/page.tsx +++ b/src/app/dev/kyc-ui/page.tsx @@ -350,33 +350,25 @@ export default function KycUiAuditPage() {

Brand new user

hasn't confirmed ID yet

- - - +

One payment method needs a document

everything works except US bank transfers — needs proof of address

- - - +

European user with deadline

SEPA needs a small update by June 29 to stay active

- - - +

Everything working

all set up, no actions needed

- - - +
)}