Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
159 lines
5.4 KiB
TypeScript
159 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { ExternalLink, HandHeart, X } from "lucide-react";
|
|
import { getWinnerImpact, type LastYearWinner } from "@/content/last-year-winners";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type Props = {
|
|
company: LastYearWinner;
|
|
className?: string;
|
|
showClose?: boolean;
|
|
onClose?: () => void;
|
|
};
|
|
|
|
function externalLinkProps(href: string) {
|
|
return href.startsWith("http") ? { target: "_blank" as const, rel: "noopener noreferrer" } : {};
|
|
}
|
|
|
|
function FounderPhoto({ company }: { company: LastYearWinner }) {
|
|
const [failed, setFailed] = useState(false);
|
|
const src = company.founderImageSrc;
|
|
|
|
if (src && !failed) {
|
|
return (
|
|
<Image
|
|
src={src}
|
|
alt=""
|
|
width={48}
|
|
height={48}
|
|
className="size-12 shrink-0 rounded-full border-2 border-[#1a5c38]/15 object-cover"
|
|
onError={() => setFailed(true)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const initials =
|
|
company.initials ??
|
|
(company.name
|
|
? company.name
|
|
.split(/\s+/)
|
|
.map((w) => w[0])
|
|
.join("")
|
|
.slice(0, 2)
|
|
.toUpperCase()
|
|
: "GR");
|
|
|
|
return (
|
|
<div
|
|
className="flex size-12 shrink-0 items-center justify-center rounded-full border-2 border-[#1a5c38]/15 bg-[#f0f5f2] text-sm font-bold text-[#1a5c38]"
|
|
aria-hidden
|
|
>
|
|
{initials}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function LastYearWinnerTip({ company, className, showClose, onClose }: Props) {
|
|
const impact = getWinnerImpact(company);
|
|
const headline = impact.metrics.find((m) => m.highlight) ?? impact.metrics[0];
|
|
const supporting = impact.metrics.filter((m) => m !== headline).slice(0, 2);
|
|
const name = company.name ?? "GRV Summit alumni";
|
|
const viewHref = impact.links.view ?? impact.links.website;
|
|
const donateHref = impact.links.donate;
|
|
|
|
const showFooter = showClose || viewHref || donateHref;
|
|
|
|
return (
|
|
<div className={cn("box-border w-72 max-w-full shrink-0 overflow-hidden", className)}>
|
|
<div className="flex min-w-0 gap-2.5 px-3 pt-3 pb-2">
|
|
<FounderPhoto company={company} />
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-[11px] font-semibold uppercase tracking-wider text-[#1a5c38]">
|
|
{name}
|
|
</p>
|
|
{company.founderName ? (
|
|
<p className="truncate text-[10px] text-[#3d5248]/80">{company.founderName}</p>
|
|
) : null}
|
|
<p className="mt-1 line-clamp-2 text-[11px] leading-snug text-[#3d5248]">
|
|
{impact.summary}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{headline ? (
|
|
<div className="mx-3 mb-2 overflow-hidden rounded-lg border border-[#1a5c38]/12 bg-[#f0f5f2] px-2.5 py-2 text-center">
|
|
<p className="truncate text-[10px] font-bold uppercase tracking-wider text-[#1a5c38]/75">
|
|
{headline.label}
|
|
</p>
|
|
<p className="winner-impact-value mt-0.5 truncate font-display text-xl font-extrabold tracking-tight text-[#0d3d26]">
|
|
{headline.value}
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
|
|
{supporting.length > 0 ? (
|
|
<dl className="grid min-w-0 grid-cols-2 gap-1.5 px-3 pb-2">
|
|
{supporting.map((m) => (
|
|
<div key={m.label} className="min-w-0 rounded-md bg-[#f7faf8] px-1.5 py-1.5 text-center">
|
|
<dt className="truncate text-[9px] font-semibold uppercase tracking-wide text-[#1a5c38]/65">
|
|
{m.label}
|
|
</dt>
|
|
<dd className="truncate text-xs font-bold text-[#0d3d26]">{m.value}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
) : null}
|
|
|
|
{showFooter ? (
|
|
<div
|
|
className={cn(
|
|
"flex w-full min-w-0 items-center gap-1.5 border-t border-[#1a5c38]/10 bg-[#f7faf8]/80 px-2.5 py-2",
|
|
showClose && onClose ? "justify-between" : "justify-end"
|
|
)}
|
|
>
|
|
{showClose && onClose ? (
|
|
<button
|
|
type="button"
|
|
data-winner-interactive
|
|
onClick={onClose}
|
|
className="inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-[#1a5c38]/15 bg-white text-[#1a5c38] shadow-sm transition-colors hover:bg-[#f0f5f2]"
|
|
aria-label="Close impact details"
|
|
>
|
|
<X className="size-3.5" aria-hidden />
|
|
</button>
|
|
) : null}
|
|
<div className="flex shrink-0 items-center gap-1.5">
|
|
{viewHref ? (
|
|
<Button
|
|
variant="outline"
|
|
size="icon-sm"
|
|
className="rounded-full border-[#1a5c38]/20 text-[#1a5c38] hover:bg-[#f0f5f2]"
|
|
asChild
|
|
>
|
|
<Link href={viewHref} aria-label="View startup" {...externalLinkProps(viewHref)}>
|
|
<ExternalLink className="size-4" />
|
|
</Link>
|
|
</Button>
|
|
) : null}
|
|
{donateHref ? (
|
|
<Button
|
|
size="icon-sm"
|
|
className="rounded-full bg-[#1a5c38] text-white hover:bg-[#0d3d26]"
|
|
asChild
|
|
>
|
|
<Link href={donateHref} aria-label="Donate" {...externalLinkProps(donateHref)}>
|
|
<HandHeart className="size-4" />
|
|
</Link>
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|