/* Me2You - Trust & Verification: Verified badge, ID verify, proof of ownership, safe meet-ups, report/block, 2FA */
/* global React, Icon, panelStyle, btnPrimary, btnGhost, btnSmall, btnDanger, LeafletMap, CITY_COORDS */
const { useState } = React;
/* --- Verified Badge --- */
function VerifiedBadge({ level = 'verified', size = 14 }) {
const map = {
verified: { color:'var(--info)', label:'ID verified', icon:'shield-check' },
trusted: { color:'var(--success)', label:'Trusted seller', icon:'check-circle' },
pro: { color:'var(--brand-orange)', label:'Pro seller', icon:'star' },
};
const c = map[level] || map.verified;
return (
{c.label}
);
}
/* --- ID Verification Wizard (FICA-style) --- */
function IDVerifyWizard({ onComplete, onBack }) {
const [step, setStep] = useState(1);
const [idType, setIdType] = useState('za_id');
const [idNumber, setIdNumber] = useState('');
const [docFront, setDocFront] = useState(null);
const [selfie, setSelfie] = useState(null);
const frontRef = React.useRef(null);
const selfieRef = React.useRef(null);
const readFile = (file, set) => {
const r = new FileReader();
r.onload = () => set(r.result);
r.readAsDataURL(file);
};
return (
Back
Verify your identity
Verified sellers get a badge, list vehicles and property, and earn buyer trust. Takes about 3 minutes.
{['Document','Photos','Selfie','Done'].map((s,i) => (
))}
{step === 1 && (
What document will you use?
{[
{key:'za_id', label:'South African ID book / smart card', desc:'13-digit ID number'},
{key:'passport', label:'Passport', desc:'For SA residents and visitors'},
{key:'drivers', label:"Driver's licence", desc:'Card with photo and 13-digit ID'},
].map(opt => (
setIdType(opt.key)} style={{
padding:14,borderRadius:'var(--radius-md)',cursor:'pointer',marginBottom:8,
border: idType===opt.key ? '2px solid var(--brand-orange)' : '1.5px solid var(--ink-200)',
background: idType===opt.key ? 'var(--brand-orange-50)' : 'transparent',
}}>
{opt.label}
{opt.desc}
))}
ID number
setIdNumber(e.target.value)}
placeholder="9001011234086" maxLength={13}
style={{width:'100%',padding:'13px 14px',border:'1.5px solid var(--ink-200)',borderRadius:'var(--radius-sm)',
fontFamily:'var(--font-mono)',fontSize:15,boxSizing:'border-box'}}/>
setStep(2)} disabled={idNumber.length < 6}
style={{...btnPrimary,marginTop:16,opacity: idNumber.length < 6 ? 0.5 : 1}}>Next
)}
{step === 2 && (
Photo of your document
e.target.files[0] && readFile(e.target.files[0], setDocFront)} style={{display:'none'}}/>
frontRef.current?.click()} style={{
border: docFront ? '2px solid var(--success)' : '2px dashed var(--brand-orange)',
borderRadius:'var(--radius-lg)',padding: docFront ? 0 : 40,minHeight:200,cursor:'pointer',
background: docFront ? 'transparent' : 'var(--brand-orange-50)',
display:'flex',alignItems:'center',justifyContent:'center',position:'relative',overflow:'hidden'
}}>
{docFront ? (
) : (
Tap to scan document
Make sure all 4 corners are visible
)}
setStep(1)} style={btnGhost}>Back
setStep(3)} disabled={!docFront}
style={{...btnPrimary,opacity: !docFront ? 0.5 : 1}}>Next
)}
{step === 3 && (
Selfie holding your document
We use this to match your face to your ID. Used once for verification, never shared.
e.target.files[0] && readFile(e.target.files[0], setSelfie)} style={{display:'none'}}/>
selfieRef.current?.click()} style={{
border: selfie ? '2px solid var(--success)' : '2px dashed var(--brand-orange)',
borderRadius:'var(--radius-lg)',padding: selfie ? 0 : 40,minHeight:200,cursor:'pointer',
background: selfie ? 'transparent' : 'var(--brand-orange-50)',
display:'flex',alignItems:'center',justifyContent:'center',position:'relative',overflow:'hidden'
}}>
{selfie ? (
) : (
Tap to take selfie
Hold your ID next to your face
)}
setStep(2)} style={btnGhost}>Back
setStep(4)} disabled={!selfie}
style={{...btnPrimary,opacity: !selfie ? 0.5 : 1}}>Submit for review
)}
{step === 4 && (
Submitted for review
Your documents are encrypted and being reviewed. You'll get a notification within 24 hours.
Most verifications complete in under 4 hours.
Back to my account
)}
);
}
/* --- Proof of Ownership uploader (for vehicles/property) --- */
function ProofOfOwnership({ kind = 'vehicle' }) {
const map = {
vehicle: { docs: ['Vehicle registration certificate (NaTIS)','Most recent licence disc','Proof of insurance (optional)'], title:'Prove you own this vehicle' },
property: { docs: ['Title deed or rental agreement','Latest municipal rates account','Proof of address (Eskom / water bill)'], title:'Prove you own this property' },
};
const c = map[kind];
const [uploaded, setUploaded] = useState({});
return (
{c.title}
Required to publish {kind} listings. Documents are reviewed by Me2You admins, encrypted, and never shown to buyers.
{c.docs.map((d,i) => (
{d}
{uploaded[i] ? 'Uploaded - pending review' : 'JPG, PNG or PDF - max 5 MB'}
setUploaded({...uploaded, [i]: !uploaded[i]})} style={{...btnSmall,fontSize:12,padding:'6px 12px'}}>
{uploaded[i] ? 'Re-upload' : 'Upload'}
))}
uploaded[k]).length < 2}>
Submit for verification
);
}
/* --- Safe Meet-up Locations Map --- */
const SAFE_LOCATIONS = [
{ name:'Sandton SAPS', type:'Police station', lat:-26.1051, lng:28.0532, hours:'24/7' },
{ name:'Soweto Police Station', type:'Police station', lat:-26.2683, lng:27.8543, hours:'24/7' },
{ name:'Maponya Mall', type:'Shopping mall', lat:-26.2734, lng:27.8941, hours:'9am-7pm' },
{ name:'Sandton City', type:'Shopping mall', lat:-26.1085, lng:28.0568, hours:'9am-9pm' },
{ name:'Cresta Mall', type:'Shopping mall', lat:-26.1334, lng:27.9744, hours:'9am-7pm' },
{ name:'Hatfield SAPS', type:'Police station', lat:-25.7491, lng:28.2389, hours:'24/7' },
{ name:'Menlyn Park', type:'Shopping mall', lat:-25.7838, lng:28.2774, hours:'9am-9pm' },
];
function SafeMeetupMap({ buyerCoords = [-26.2041, 28.0473], onPick }) {
const [chosen, setChosen] = useState(null);
const markers = SAFE_LOCATIONS.map(l => ({
lat:l.lat, lng:l.lng,
label: l.type === 'Police station' ? 'P' : 'M',
color: l.type === 'Police station' ? '#2D7AC7' : '#2F9E5A',
popup: `${l.name} ${l.type} ${l.hours}`,
}));
return (
For self-collect orders we recommend meeting at a police station or busy shopping mall during daylight hours. Never meet at a private residence.
{SAFE_LOCATIONS.slice(0,4).map((l,i) => (
{ setChosen(l); if (onPick) onPick(l); }} style={{
padding:12,borderRadius:'var(--radius-md)',cursor:'pointer',textAlign:'left',
border: chosen?.name === l.name ? '2px solid var(--brand-orange)' : '1.5px solid var(--ink-200)',
background: chosen?.name === l.name ? 'var(--brand-orange-50)' : 'var(--paper)',
}}>
{l.name}
{l.type} - {l.hours}
))}
);
}
/* --- Report / Block User Modal --- */
function ReportUserModal({ userName, onClose, onSubmit }) {
const [reason, setReason] = useState('');
const [details, setDetails] = useState('');
const [block, setBlock] = useState(true);
return (
);
}
/* --- 2FA Setup --- */
function TwoFactorSetup({ onComplete }) {
const [enabled, setEnabled] = useState(false);
const [method, setMethod] = useState('sms');
const [code, setCode] = useState(['','','','','','']);
const [verified, setVerified] = useState(false);
if (verified) return (
Two-factor authentication is on
You'll need a code from {method === 'sms' ? 'SMS' : 'your authenticator app'} every time you sign in from a new device.
);
return (
Two-factor authentication
Adds an extra layer of security. Required for sellers handling vehicles or property.
{!enabled ? (
setEnabled(true)} style={btnPrimary}>
Enable 2FA
) : (
<>
Verification method
{[
{key:'sms',label:'SMS to phone',desc:'071 234 5678'},
{key:'app',label:'Authenticator app',desc:'Google Authenticator, Authy'},
].map(m => (
setMethod(m.key)} style={{
flex:1,padding:14,borderRadius:'var(--radius-md)',cursor:'pointer',textAlign:'left',
border: method===m.key ? '2px solid var(--brand-orange)' : '1.5px solid var(--ink-200)',
background: method===m.key ? 'var(--brand-orange-50)' : 'var(--paper)',
}}>
{m.label}
{m.desc}
))}
{method === 'sms' ? 'Code sent to 071 *** 5678' : 'Scan this QR with your app:'}
{method === 'app' && (
)}
Enter 6-digit code
{[0,1,2,3,4,5].map(i => (
{
const v = e.target.value.replace(/\D/g,'');
const nc = [...code]; nc[i] = v; setCode(nc);
if (v && i < 5) e.target.nextSibling?.focus();
}}
style={{width:42,height:50,textAlign:'center',fontSize:20,fontFamily:'var(--font-mono)',fontWeight:700,
border:'1.5px solid var(--ink-200)',borderRadius:'var(--radius-sm)',background:'var(--paper)'}}/>
))}
setVerified(true)} disabled={code.join('').length < 6}
style={{...btnPrimary,width:'100%',justifyContent:'center',opacity: code.join('').length < 6 ? 0.5 : 1}}>
Verify and enable
>
)}
);
}
Object.assign(window, { VerifiedBadge, IDVerifyWizard, ProofOfOwnership, SafeMeetupMap, ReportUserModal, TwoFactorSetup, SAFE_LOCATIONS });