Files
doormilxpress_astryx/src/components/krow/KrowIdPreview.jsx
2026-08-07 23:53:15 +05:30

136 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { useWorkerProfile } from '@/lib/krowHooks';
import { getTalentScore, getScoreBand, getGrowth } from '@/lib/talentHome';
import { ShieldCheck, Smartphone, Image as ImageIcon, QrCode, Sparkles } from 'lucide-react';
import { toast } from 'react-hot-toast';
import { Image } from '@/components/ui/image';
const clamp = (n, min = 0, max = 100) => Math.max(min, Math.min(max, Math.round(Number(n) || 0)));
const toFICO = (s) => 300 + Math.round((clamp(s) / 100) * 550);
function buildVCard(profile) {
const name = profile.full_name || 'KROW Talent';
const [first, ...rest] = name.split(' ');
const last = rest.join(' ');
const lines = [
'BEGIN:VCARD', 'VERSION:3.0',
`N:${last};${first};;;`, `FN:${name}`,
profile.current_position ? `TITLE:${profile.current_position}` : '',
profile.phone ? `TEL;TYPE=CELL:${profile.phone}` : '',
profile.email ? `EMAIL:${profile.email}` : '',
`URL:https://krow.app/me`,
`NOTE:KROW ID ${(profile.id || '').slice(-6).toUpperCase()}`,
'END:VCARD',
].filter(Boolean);
return lines.join('\n');
}
function download(filename, content, type) {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; a.click();
URL.revokeObjectURL(url);
}
export default function KrowIdPreview() {
const { data: profile, isLoading } = useWorkerProfile();
if (isLoading || !profile) {
return <div className="flex justify-center py-10 text-xs text-[#6B7280]">Loading KROW ID…</div>;
}
const score = getTalentScore(profile);
const band = getScoreBand(score);
const growth = getGrowth(profile);
const fico = toFICO(score);
const shortId = (profile.id || '').slice(-6).toUpperCase();
const name = profile.full_name || 'KROW Talent';
const title = profile.current_position || profile.desired_position || 'Open to work';
const initials = name.split(' ').map(p => p[0]).slice(0, 2).join('').toUpperCase();
const vcard = buildVCard(profile);
const qrSrc = `https://api.qrserver.com/v1/create-qr-code/?margin=0&size=120x120&data=${encodeURIComponent(vcard)}`;
const addToContacts = () => {
download(`${name.replace(/\s+/g, '_')}.vcf`, vcard, 'text/vcard');
toast.success('Contact file downloaded.');
};
return (
<div className="flex justify-center">
<div className="relative w-full max-w-[360px]" style={{ perspective: '1200px' }}>
{/* depth layers */}
<div aria-hidden className="absolute inset-0 rounded-3xl bg-white/50 border border-white/60 backdrop-blur-xl shadow-[0_18px_50px_-20px_rgba(10,57,223,0.3)]"
style={{ transform: 'translateY(12px) translateX(10px) rotateY(-8deg) rotateX(5deg)' }} />
<div aria-hidden className="absolute inset-0 rounded-3xl bg-white/40 border border-white/70 backdrop-blur-xl shadow-[0_14px_40px_-16px_rgba(10,57,223,0.25)]"
style={{ transform: 'translateY(6px) translateX(5px) rotateY(-4deg) rotateX(2deg)' }} />
{/* front pass */}
<div className="relative rounded-3xl border border-white/80 backdrop-blur-2xl shadow-[0_20px_60px_-22px_rgba(10,57,223,0.4)] overflow-hidden"
style={{ background: 'linear-gradient(160deg, rgba(255,255,255,0.94) 0%, rgba(244,247,255,0.9) 100%)' }}>
<div aria-hidden className="pointer-events-none absolute inset-0" style={{ background: 'linear-gradient(115deg, transparent 30%, rgba(10,57,223,0.05) 45%, rgba(249,229,71,0.06) 55%, transparent 70%)' }} />
<div className="relative p-5">
{/* header row */}
<div className="flex items-center gap-3">
<div className="w-14 h-14 rounded-full overflow-hidden border-2 border-white shadow bg-[#EFF6FF] flex items-center justify-center shrink-0">
{profile.selfie_url ? (
<Image src={profile.selfie_url} alt={name} className="w-full h-full object-cover" fittingType="fill" />
) : (
<span className="text-[18px] font-bold text-[#0A39DF]">{initials}</span>
)}
</div>
<div className="min-w-0 text-left">
<p className="text-[9px] font-semibold uppercase tracking-[0.12em] text-[#0A39DF]">KROW ID · {shortId}</p>
<h2 className="text-[16px] font-bold text-[#0F172A] tracking-tight leading-tight truncate">{name}</h2>
<p className="text-[11px] text-[#6B7280] truncate">{title}</p>
</div>
<ShieldCheck className="w-4 h-4 text-[#10B981] shrink-0" />
</div>
{/* score + factors row */}
<div className="mt-4 flex items-center gap-4">
<div className="text-center shrink-0">
<p className="text-[30px] font-bold text-[#0F172A] leading-none tracking-tight">{fico}</p>
<span className="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-bold mt-1"
style={{ color: band.color, background: `${band.color}14` }}>
<Sparkles className="w-2.5 h-2.5" /> {band.label}
</span>
<p className="text-[10px] font-semibold text-[#16A34A] mt-1">↑ +{growth} mo</p>
</div>
<div className="flex-1 grid grid-cols-2 gap-1.5">
{[
['Reliability', clamp(profile.reliability_score)],
['Attendance', clamp(profile.attendance_score ?? 100)],
['Performance', clamp(profile.performance_score)],
['XP', Number(profile.xp) || 0],
].map(([label, value]) => (
<span key={label} className="inline-flex items-center justify-between gap-1 rounded-lg bg-white/70 border border-[#E6ECF5] px-2 py-1 text-[10px]">
<span className="text-[#9CA3AF]">{label}</span>
<span className="font-semibold text-[#0A39DF]">{value}</span>
</span>
))}
</div>
</div>
{/* footer: QR + actions */}
<div className="mt-4 flex items-center gap-3">
<div className="rounded-lg bg-white p-1 border border-[#E6ECF5] shadow-sm shrink-0">
<img src={qrSrc} alt="KROW Identity QR" width={48} height={48} className="block" />
</div>
<div className="flex-1 grid grid-cols-2 gap-1.5">
<button onClick={addToContacts} className="flex items-center justify-center gap-1 h-8 rounded-lg bg-[#0A39DF] text-white text-[10px] font-semibold hover:bg-[#082EB4] transition-colors">
<Smartphone className="w-3 h-3" /> Contacts
</button>
<button onClick={() => toast('Open My KROW ID page to save the full pass.', { icon: 'ℹ️' })} className="flex items-center justify-center gap-1 h-8 rounded-lg bg-white border border-[#E6ECF5] text-[#0F172A] text-[10px] font-semibold hover:border-[#0A39DF] transition-colors">
<ImageIcon className="w-3 h-3 text-[#0A39DF]" /> Save
</button>
</div>
</div>
<p className="text-[9px] text-[#9CA3AF] mt-2 flex items-center gap-1 justify-center"><QrCode className="w-2.5 h-2.5" /> Scan to share your verified KROW Identity</p>
</div>
</div>
</div>
</div>
);
}