136 lines
7.3 KiB
JavaScript
136 lines
7.3 KiB
JavaScript
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>
|
||
);
|
||
} |