← back to Nineoh Guide

apps/web/components/RightsImage.tsx

78 lines

import { type Asset, assetIsRenderable } from "@nineoh/core";

/**
 * The pending-image placeholder pipeline.
 *
 * An image renders ONLY if it has a cleared, non-PENDING license and a file.
 * Otherwise we show a polished gradient placeholder with a PENDING pill — the
 * page never looks broken while an image license is still drafted for approval.
 * This is the UI half of the rights-ledger enforced in the assets table.
 */
export function RightsImage({
  asset,
  alt,
  width = 320,
  height = 200,
}: {
  asset: Asset | null | undefined;
  alt: string;
  width?: number;
  height?: number;
}) {
  if (asset && assetIsRenderable(asset)) {
    return (
      <figure style={{ margin: 0 }}>
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={asset.fileUrl!}
          alt={alt}
          width={width}
          height={height}
          style={{ objectFit: "cover", borderRadius: 8, display: "block" }}
        />
        {asset.attributionText ? (
          <figcaption style={{ fontSize: 11, color: "#888", marginTop: 4 }}>
            {asset.attributionText}
          </figcaption>
        ) : null}
      </figure>
    );
  }

  // Deterministic hue from the alt text so placeholders look intentional.
  const hue =
    Math.abs(
      Array.from(alt).reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 7)
    ) % 360;
  return (
    <div
      role="img"
      aria-label={`${alt} (image pending license clearance)`}
      style={{
        width,
        height,
        borderRadius: 8,
        background: `linear-gradient(135deg, hsl(${hue} 45% 82%), hsl(${(hue + 40) % 360} 45% 68%))`,
        display: "flex",
        alignItems: "flex-end",
        padding: 8,
        boxSizing: "border-box",
      }}
    >
      <span
        style={{
          fontSize: 10,
          fontWeight: 700,
          letterSpacing: 0.5,
          color: "#fff",
          background: "rgba(0,0,0,0.35)",
          padding: "2px 6px",
          borderRadius: 4,
        }}
      >
        IMAGE PENDING
      </span>
    </div>
  );
}