pr_01m47d15m3e54sn21z27rpy5n9/apps/web/app/components/production-shot.tsx

89 lines3,097 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import { Globe } from "lucide-react";
2import { useEffect, useRef, useState } from "react";
3
4import { cn } from "../lib/cn";
5
6/**
7 * A thumbnail of production as it is now, linking to it. The screenshot is
8 * taken once per deploy; until it has loaded, or when there is none yet, a
9 * quiet frame with the address stands in.
10 */
11export function ProductionShot({
12 src,
13 href,
14 label,
15 className,
16}: {
17 /** Null when nothing is deployed yet. */
18 src: string | null;
19 href: string | null;
20 label: string;
21 className?: string;
22}) {
23 const image = useRef<HTMLImageElement>(null);
24 const [state, setState] = useState<"loading" | "loaded" | "failed">("loading");
25
26 // An image that settled before hydration fired its events unheard.
27 useEffect(() => {
28 const img = image.current;
29 if (!img?.complete) return;
30 setState(img.naturalWidth > 0 ? "loaded" : "failed");
31 }, [src]);
32
33 const frame = (
34 <span
35 className={cn(
36 "relative block aspect-[16/10] w-full overflow-hidden rounded-lg border border-line bg-bg",
37 href && "transition-colors group-hover:border-line-strong",
38 )}
39 >
40 {state !== "loaded" && <Placeholder label={label} waiting={src != null && state === "loading"} />}
41 {src && state !== "failed" && (
42 <img
43 ref={image}
44 src={src}
45 alt={`Screenshot of ${label}`}
46 width={1280}
47 height={800}
48 decoding="async"
49 onLoad={() => setState("loaded")}
50 onError={() => setState("failed")}
51 className={cn(
52 "absolute inset-0 size-full object-cover object-top transition-opacity duration-300",
53 state === "loaded" ? "opacity-100" : "opacity-0",
54 )}
55 />
56 )}
57 </span>
58 );
59
60 return href ? (
61 <a href={href} className={cn("group block", className)} aria-label={`Visit ${label}`}>
62 {frame}
63 </a>
64 ) : (
65 <div className={className}>{frame}</div>
66 );
67}
68
69function Placeholder({ label, waiting }: { label: string; waiting: boolean }) {
70 return (
71 <span className="absolute inset-0 flex flex-col">
72 <span className="flex items-center gap-1.5 border-b border-line bg-surface px-2.5 py-1.5">
73 <span className="size-1.5 rounded-full bg-line-strong" />
74 <span className="size-1.5 rounded-full bg-line-strong" />
75 <span className="size-1.5 rounded-full bg-line-strong" />
76 <span className="ml-1.5 truncate rounded bg-bg px-2 py-px font-mono text-[0.625rem] text-faint">{label}</span>
77 </span>
78 <span className="relative flex grow flex-col gap-2 p-3.5">
79 <span className="h-2 w-2/5 rounded-full bg-line/80" />
80 <span className="h-1.5 w-4/5 rounded-full bg-line/50" />
81 <span className="h-1.5 w-3/5 rounded-full bg-line/50" />
82 <span className="mt-auto flex items-center gap-1.5 text-[0.6875rem] text-faint">
83 <Globe size={12} className={waiting ? "animate-pulse" : undefined} />
84 {waiting ? "Taking a screenshot…" : "A screenshot appears after the next deploy"}
85 </span>
86 </span>
87 </span>
88 );
89}