Repository navigation
Expand file tree
/
Copy pathadmin-kyc.html
More file actions
11 lines (11 loc) · 9.25 KB
/
Copy pathadmin-kyc.html
File metadata and controls
11 lines (11 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
<!doctype html><html lang="en"><head><style id="auth-gate">html{visibility:hidden}</style><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>HomeSync Admin — KYC Review</title><link rel="stylesheet" href="assets/css/admin.css"><script src="assets/js/supabase-config.js"></script><script src="assets/js/vendor/supabase.js"></script><script src="assets/js/supabase-client.js"></script><script src="assets/js/auth.js"></script><script src="assets/js/ui-helpers.js"></script><script src="assets/js/api.js"></script><script src="assets/js/admin-shell.js"></script><style>.kyc-admin{padding:0 28px 60px}.filters{display:flex;gap:10px;flex-wrap:wrap;margin:18px 0}.filters select,.filters input{padding:10px 12px;border:1px solid var(--line,#ddd);border-radius:10px;background:var(--card,#fff);color:var(--ink,#222)}.kyc-table{width:100%;border-collapse:collapse}.kyc-table th,.kyc-table td{padding:13px 10px;border-bottom:1px solid var(--line,#eee);text-align:left;vertical-align:top}.kyc-table th{font-size:12px;text-transform:uppercase;color:var(--ink-soft,#777)}.status{display:inline-flex;padding:5px 9px;border-radius:999px;font-size:11px;font-weight:800;text-transform:capitalize}.status.pending{background:#fff3cd;color:#8a6500}.status.approved{background:#dcfce7;color:#166534}.status.rejected,.status.needs_resubmission{background:#fee2e2;color:#991b1b}.actions{display:flex;gap:7px;flex-wrap:wrap}.btn-danger{border-color:#ef4444!important;color:#dc2626!important}.review-modal{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;place-items:center;padding:20px;z-index:100}.review-modal.open{display:grid}.modal-card{background:var(--card,#fff);color:var(--ink,#222);width:min(620px,100%);max-height:90vh;overflow:auto;border-radius:18px;padding:24px}.modal-card textarea{width:100%;min-height:100px;padding:12px;border:1px solid #ddd;border-radius:10px;box-sizing:border-box;margin:8px 0 14px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap}.risk{padding:12px;background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;margin:12px 0;line-height:1.5}.live{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:800;color:#10b981}.live i{width:7px;height:7px;border-radius:50%;background:#10b981;display:inline-block}.error{padding:14px;border:1px solid #ef4444;border-radius:12px;color:#b91c1c;background:#fef2f2}.pending-count{font-size:28px;font-weight:900;color:#10b981}</style></head><body><script>AUTH.requireAdmin();</script><main class="kyc-admin"><header class="top"><h1>🛡️ KYC verification</h1><div class="topright"><span class="live"><i></i>LIVE QUEUE</span><a class="pill-btn ghost" href="admin.html">Dashboard</a><a class="pill-btn ghost" href="dashboard.html">App</a></div></header><section class="panel"><div class="panel-head"><div><h3>Identity review queue</h3><div id="stats" style="color:var(--ink-soft)">Loading…</div></div></div><div class="filters"><select id="status"><option value="pending">Pending</option><option value="needs_resubmission">Needs resubmission</option><option value="rejected">Rejected</option><option value="approved">Approved</option><option value="all">All</option></select><input id="search" placeholder="Search name or email"><button class="pill-btn pink" id="refresh">Refresh</button></div><div style="overflow:auto"><table class="kyc-table"><thead><tr><th>User</th><th>Document</th><th>Submitted</th><th>Status</th><th>Document</th><th>Action</th></tr></thead><tbody id="tbody"><tr><td colspan="6">Loading…</td></tr></tbody></table></div></section></main><div class="review-modal" id="modal"><div class="modal-card"><h2 id="modal-title">Review KYC</h2><div id="modal-meta"></div><div class="risk">⚠️ Identity documents are highly sensitive. Review only what is necessary. Do not download, screenshot, copy, or share documents outside authorized verification workflows.</div><label>Admin note</label><textarea id="note" placeholder="Internal review note…"></textarea><label>Reason if rejecting / requesting resubmission</label><textarea id="reason" placeholder="Tell the user exactly what needs correction…"></textarea><div class="modal-actions"><button class="pill-btn ghost" id="cancel">Cancel</button><button class="pill-btn ghost btn-danger" id="resubmit">Needs resubmission</button><button class="pill-btn ghost btn-danger" id="reject">Reject</button><button class="pill-btn pink" id="approve">Approve & verify</button></div></div></div><script>
let rows=[];let selected=null;let timer=null;
function client(){if(!window.sb)throw new Error('HomeSync Supabase client is not initialized.');return window.sb}
async function load(){try{const status=document.getElementById('status').value;let q=client().from('kyc_submissions').select('id,user_id,document_type,document_name,storage_path,status,submitted_at,reviewed_at,rejection_reason,admin_note,document_last4,created_at').order('submitted_at',{ascending:false});if(status!=='all')q=q.eq('status',status);const {data,error}=await q;if(error)throw error;const ids=[...new Set((data||[]).map(r=>r.user_id).filter(Boolean))];let profiles=[];if(ids.length){const p=await client().from('profiles').select('id,full_name,email,city,is_verified').in('id',ids);if(p.error)throw p.error;profiles=p.data||[]}const byId=Object.fromEntries(profiles.map(p=>[p.id,p]));rows=(data||[]).map(r=>({...r,profiles:byId[r.user_id]||null}));render()}catch(error){console.error('KYC queue:',error);document.getElementById('tbody').innerHTML=`<tr><td colspan="6"><div class="error">Unable to load KYC queue: ${esc(error.message||error)}</div></td></tr>`;document.getElementById('stats').textContent='KYC queue error — check admin permissions';}}
function render(){const term=document.getElementById('search').value.trim().toLowerCase();const filtered=rows.filter(r=>!term||`${r.profiles?.full_name||''} ${r.profiles?.email||''}`.toLowerCase().includes(term));const pending=rows.filter(r=>r.status==='pending').length;document.getElementById('stats').innerHTML=`<span class="pending-count">${pending}</span> pending · ${filtered.length} submission${filtered.length===1?'':'s'} in this queue`;document.getElementById('tbody').innerHTML=filtered.length?filtered.map(r=>`<tr><td><strong>${esc(r.profiles?.full_name||'Unknown')}</strong><br><small>${esc(r.profiles?.email||'')} · ${esc(r.profiles?.city||'')}</small></td><td><strong>${esc(label(r.document_type))}</strong><br><small>${esc(r.document_name||'')}${r.document_last4?' · ••••'+esc(r.document_last4):''}</small></td><td>${new Date(r.submitted_at||r.created_at).toLocaleString()}</td><td><span class="status ${esc(r.status)}">${esc(r.status.replace('_',' '))}</span></td><td><button class="pill-btn ghost sm" data-view="${r.id}">View securely</button></td><td>${r.status==='pending'||r.status==='needs_resubmission'?`<button class="pill-btn pink sm" data-review="${r.id}">Review</button>`:'—'}</td></tr>`).join(''):'<tr><td colspan="6" style="padding:30px;text-align:center">No submissions in this status.</td></tr>'}
function openReview(id){selected=rows.find(x=>x.id===id);if(!selected)return;document.getElementById('modal-title').textContent=`Review ${label(selected.document_type)}`;document.getElementById('modal-meta').innerHTML=`<strong>${esc(selected.profiles?.full_name||'Unknown')}</strong> · ${esc(selected.profiles?.email||'')}<br><small>Submitted ${new Date(selected.submitted_at||selected.created_at).toLocaleString()}</small>`;document.getElementById('note').value=selected.admin_note||'';document.getElementById('reason').value=selected.rejection_reason||'';document.getElementById('modal').classList.add('open')}
async function viewDoc(id){const r=rows.find(x=>x.id===id);if(!r)return;const {data,error}=await client().storage.from('kyc-documents').createSignedUrl(r.storage_path,300);if(error){alert(error.message);return}window.open(data.signedUrl,'_blank','noopener,noreferrer')}
async function decide(status){if(!selected)return;const reason=document.getElementById('reason').value.trim();if((status==='rejected'||status==='needs_resubmission')&&!reason){alert('Give the user a clear reason.');return}const {error}=await client().rpc('review_kyc',{p_submission_id:selected.id,p_status:status,p_reason:reason||null,p_admin_note:document.getElementById('note').value.trim()||null});if(error){alert(error.message);return}document.getElementById('modal').classList.remove('open');selected=null;await load()}
document.getElementById('tbody').addEventListener('click',e=>{const v=e.target.closest('[data-view]');const r=e.target.closest('[data-review]');if(v)viewDoc(v.dataset.view);if(r)openReview(r.dataset.review)});document.getElementById('refresh').onclick=load;document.getElementById('status').onchange=load;document.getElementById('search').oninput=render;document.getElementById('cancel').onclick=()=>document.getElementById('modal').classList.remove('open');document.getElementById('approve').onclick=()=>decide('approved');document.getElementById('reject').onclick=()=>decide('rejected');document.getElementById('resubmit').onclick=()=>decide('needs_resubmission');load();timer=setInterval(load,15000);
function label(x){return ({aadhaar:'Aadhaar',passport:'Passport',driving_license:'Driving licence',voter_id:'Voter ID',pan:'PAN'})[x]||x}function esc(s){const d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML}
</script></body></html>