getcert-backend / src /components /ProjectProofCard.tsx
ggop213's picture
refactor(ui): complete platform de-slop and remove AI badges across all pages
48e2068
Raw History Blame Contribute Delete
8.2 kB
import { CheckCircle2, AlertTriangle, FolderGit2, Globe, ShieldCheck } from 'lucide-react';
import { resolveCanonicalCourseTitle } from '../utils/courseTitleResolver';
type ProjectProofCardProps = {
project: {
certificateId: string | null;
skill: string;
projectTitle: string | null;
projectType: string | null;
difficultyLevel: 'beginner' | 'intermediate' | 'advanced';
githubRepoUrl: string | null;
liveDemoUrl: string | null;
finalScore: number;
commitCount: number;
fileCount: number;
passConfidence: number;
trustSignals: {
commitCount: number;
validationPassed: boolean;
readmeScore: number;
projectType: string | null;
};
validationSummary: {
overallPassed: boolean;
commitPassed: boolean;
readmePassed: boolean;
proofPassed: boolean;
commitCount: number;
fileCount: number;
meaningfulFileCount: number;
readmeRichness: number;
suspiciousSignals: string[];
} | null;
};
};
export function ProjectProofCard({ project }: ProjectProofCardProps) {
const verificationUrl = project.certificateId ? `/verify/${encodeURIComponent(project.certificateId)}` : null;
const displayTitle = project.projectTitle || resolveCanonicalCourseTitle(project.skill);
return (
<article className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-6 shadow-sm font-sans text-slate-900 dark:text-white">
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded">
Level: {project.difficultyLevel}
</span>
<span className="text-xs text-slate-400 dark:text-slate-500">
{resolveCanonicalCourseTitle(project.skill)} · {project.projectType || 'General'}
</span>
</div>
<h4 className="text-lg font-bold text-slate-900 dark:text-white tracking-tight">{displayTitle}</h4>
</div>
<span className={`inline-flex items-center gap-1.5 px-3 py-1 text-xs font-semibold rounded-full border ${
project.trustSignals.validationPassed
? 'bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800'
: 'bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-700'
}`}>
{project.trustSignals.validationPassed ? (
<>
<CheckCircle2 className="w-3.5 h-3.5" />
<span>Validation Passed</span>
</>
) : (
<>
<AlertTriangle className="w-3.5 h-3.5" />
<span>Needs Review</span>
</>
)}
</span>
</div>
<div className="mb-5 grid grid-cols-2 gap-3 text-xs md:grid-cols-4">
<div className="rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50 dark:bg-slate-800/60 p-3">
<p className="text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Score</p>
<p className="text-xl font-bold text-slate-900 dark:text-white mt-0.5">{project.finalScore}%</p>
</div>
<div className="rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50 dark:bg-slate-800/60 p-3">
<p className="text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Commits</p>
<p className="text-xl font-bold text-slate-900 dark:text-white mt-0.5">{project.commitCount}</p>
</div>
<div className="rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50 dark:bg-slate-800/60 p-3">
<p className="text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Files</p>
<p className="text-xl font-bold text-slate-900 dark:text-white mt-0.5">{project.fileCount}</p>
</div>
<div className="rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50 dark:bg-slate-800/60 p-3">
<p className="text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Confidence</p>
<p className="text-xl font-bold text-[#132F50] dark:text-blue-400 mt-0.5">{project.passConfidence}%</p>
</div>
</div>
<div className="mb-5 flex flex-wrap gap-2 text-xs">
<span className="bg-blue-50 dark:bg-blue-950/40 text-blue-700 dark:text-blue-300 px-2.5 py-1 rounded-lg border border-blue-200 dark:border-blue-800 font-medium">
README: {project.trustSignals.readmeScore}%
</span>
<span className="bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 px-2.5 py-1 rounded-lg border border-slate-200 dark:border-slate-700 font-medium">
Type: {project.trustSignals.projectType || 'Standard'}
</span>
<span className="bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 px-2.5 py-1 rounded-lg border border-slate-200 dark:border-slate-700 font-medium">
Verified Commits: {project.trustSignals.commitCount}
</span>
</div>
{project.validationSummary && project.validationSummary.suspiciousSignals.length > 0 && (
<div className="mb-5 rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/60 p-4 text-xs text-slate-700 dark:text-slate-300">
<p className="mb-2 font-semibold uppercase tracking-wider text-slate-800 dark:text-slate-200 flex items-center gap-1.5">
<AlertTriangle className="w-4 h-4 text-amber-600 inline" />
<span>Validator Observations</span>
</p>
<ul className="space-y-1 text-xs">
{project.validationSummary.suspiciousSignals.slice(0, 3).map((signal) => (
<li key={signal}>• {signal}</li>
))}
</ul>
</div>
)}
<div className="flex flex-wrap items-center gap-2 pt-4 border-t border-slate-100 dark:border-slate-800">
{project.githubRepoUrl && (
<a
href={project.githubRepoUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 hover:bg-slate-100 dark:bg-slate-800 dark:hover:bg-slate-700 text-xs font-semibold text-slate-800 dark:text-slate-200 transition-colors"
>
<FolderGit2 className="w-3.5 h-3.5" />
<span>Repository</span>
</a>
)}
{project.liveDemoUrl && (
<a
href={project.liveDemoUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 hover:bg-slate-100 dark:bg-slate-800 dark:hover:bg-slate-700 text-xs font-semibold text-slate-800 dark:text-slate-200 transition-colors"
>
<Globe className="w-3.5 h-3.5" />
<span>Live Demo</span>
</a>
)}
{verificationUrl && (
<a
href={verificationUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#132F50] hover:bg-[#0e233d] text-white text-xs font-semibold shadow-2xs transition-colors"
>
<ShieldCheck className="w-3.5 h-3.5" />
<span>Verify Record</span>
</a>
)}
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300 border border-emerald-200 dark:border-emerald-800 text-xs font-semibold ml-auto">
<CheckCircle2 className="w-3.5 h-3.5" />
<span>Recruiter Ready</span>
</span>
</div>
</article>
);
}