← back to Stayclaim

src/components/sponsored/SponsoredCard.tsx

85 lines

/**
 * SponsoredCard — paid Promote-a-Listing unit.
 *
 * FIREWALL RULES (see plan §1 "Promote-a-Listing editorial firewall"):
 *   - The ONLY component allowed to use coral as a fill color.
 *   - Has NO TierBadge slot — A/B/C/D is reserved for historical facts.
 *   - Never writes to event, entity_place_association, or any provenance table.
 *   - Lives in src/components/sponsored/* so a lint boundary can enforce
 *     that editorial components don't import from here (and vice versa).
 *   - Always carries "Sponsored · Current listing" eyebrow + source logo.
 */

import { FREE_FOREVER } from '@/lib/flags';
import { safeHttpUrl } from '@/lib/url-guards';

type SponsoredSource = 'zillow' | 'redfin' | 'airbnb' | 'other';

const SOURCE_LABEL: Record<SponsoredSource, string> = {
  zillow: 'Zillow',
  redfin: 'Redfin',
  airbnb: 'Airbnb',
  other: 'Listing',
};

export function SponsoredCard({
  source,
  title,
  priceLabel,
  url,
  imageUrl,
}: {
  source: SponsoredSource;
  title: string;
  priceLabel?: string;
  url: string;
  imageUrl?: string;
}) {
  // Soft-disabled when FREE_FOREVER=true — pastdoor doesn't accept paid placements.
  // 2026-05-05 (refactor agent): use the canonical flag from lib/flags.ts instead
  // of re-reading process.env, so SSR/RSC builds don't drift between init-time
  // (flags.ts) and render-time (this component) values.
  if (FREE_FOREVER) return null;

  return (
    <aside
      className="relative bg-ink text-sand border border-coral/30 rounded-none p-5 font-sans"
      data-sponsored="true"
      aria-label="Sponsored current listing"
    >
      <div className="mb-3 flex items-center justify-between text-[10px] uppercase tracking-[0.15em] text-sand/60">
        <span className="inline-flex items-center gap-2">
          <span className="inline-block h-2 w-2 rounded-full bg-coral" />
          Sponsored · Current listing
        </span>
        <span className="text-sand/50">{SOURCE_LABEL[source]}</span>
      </div>

      {safeHttpUrl(imageUrl) && (
        <div className="mb-4 aspect-[16/9] overflow-hidden">
          <img src={safeHttpUrl(imageUrl)!} alt="" className="h-full w-full object-cover" />
        </div>
      )}

      <div className="mb-4">
        <h3 className="font-sans text-base font-medium text-sand leading-snug">{title}</h3>
        {priceLabel && <p className="mt-1 text-sm text-sand/70">{priceLabel}</p>}
      </div>

      <a
        href={safeHttpUrl(url) ?? '#'}
        target="_blank"
        rel="noopener noreferrer"
        className="inline-flex items-center gap-2 border border-coral px-4 py-1.5 text-xs uppercase tracking-wider text-coral hover:bg-coral hover:text-ink transition"
      >
        View on {SOURCE_LABEL[source]}
        <span aria-hidden>→</span>
      </a>

      <p className="mt-3 text-[10px] text-sand/40 leading-relaxed">
        This is paid placement. The historical record below is independent.
      </p>
    </aside>
  );
}