● Zion Boggan
repos/Drophawk/frontend/components/dashboard/DropCard.tsx
zionboggan.com ↗
97 lines · tsx
History for this file →
1
"use client";
2
 
3
import Image from "next/image";
4
import { motion, useReducedMotion } from "framer-motion";
5
import { ArrowUpRight, MapPin, Ticket } from "lucide-react";
6
import { StatusPill } from "@/components/ui/status-pill";
7
import { SourceChip } from "@/components/shared/SourceChip";
8
import { SeatView } from "./SeatView";
9
import { RelativeTime } from "@/components/shared/RelativeTime";
10
import { dropCardVariants, flashAccentKeyframes } from "@/lib/motion";
11
import { useUiStore } from "@/stores/ui";
12
import { formatPriceRange, cn } from "@/lib/utils";
13
import type { Drop } from "@/lib/types";
14
 
15
export function DropCard({ drop }: { drop: Drop }) {
16
  const reduce = useReducedMotion();
17
  const isFresh = useUiStore((s) => s.freshIds.has(drop.id));
18
  const clearFresh = useUiStore((s) => s.clearFresh);
19
  const { event } = drop;
20
  const venue = [event.venue_name, event.city].filter(Boolean).join(" · ");
21
 
22
  return (
23
    <motion.article
24
      layout
25
      variants={dropCardVariants}
26
      initial="initial"
27
      animate={
28
        isFresh && !reduce
29
          ? { ...dropCardVariants.animate, ...flashAccentKeyframes }
30
          : "animate"
31
      }
32
      exit="exit"
33
      onAnimationComplete={() => isFresh && clearFresh(drop.id)}
34
      className="ring-frame relative overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border-subtle)] bg-[var(--color-bg-elevated)] p-3.5"
35
    >
36
      {}
37
      <div className="flex items-center justify-between gap-2">
38
        <StatusPill status={event.status} />
39
        <RelativeTime iso={drop.detected_at} className="text-xs" />
40
      </div>
41
 
42
      {}
43
      <div className="mt-2 flex gap-3">
44
        <div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-[var(--radius)] bg-[var(--color-bg-inset)]">
45
          {event.image_url ? (
46
            <Image
47
              src={event.image_url}
48
              alt=""
49
              fill
50
              sizes="64px"
51
              className="object-cover"
52
              unoptimized
53
            />
54
          ) : (
55
            <div className="grid h-full w-full place-items-center text-[var(--color-fg-disabled)]">
56
              <Ticket className="h-5 w-5" aria-hidden />
57
            </div>
58
          )}
59
        </div>
60
        <div className="min-w-0 flex-1">
61
          <h3 className="truncate text-sm font-semibold text-[var(--color-fg-primary)]">{event.title}</h3>
62
          {event.subtitle ? (
63
            <p className="truncate text-xs text-[var(--color-fg-secondary)]">{event.subtitle}</p>
64
          ) : null}
65
          {venue ? (
66
            <p className="mt-0.5 flex items-center gap-1 truncate text-xs text-[var(--color-fg-tertiary)]">
67
              <MapPin className="h-3 w-3 shrink-0" aria-hidden />
68
              <span className="truncate">{venue}</span>
69
            </p>
70
          ) : null}
71
        </div>
72
      </div>
73
 
74
      {}
75
      <div className="mt-3 flex items-center gap-2 border-t border-[var(--color-border-subtle)] pt-2.5">
76
        <span className="font-mono text-sm tabular text-[var(--color-fg-primary)]">
77
          {formatPriceRange(event.price_min, event.price_max, event.currency)}
78
        </span>
79
        <SourceChip id={event.source_id} className="ml-auto" />
80
        <SeatView event={event} />
81
        <a
82
          href={`${event.buy_url}${event.buy_url.includes("?") ? "&" : "?"}drophawk=1`}
83
          target="_blank"
84
          rel="noopener noreferrer"
85
          className={cn(
86
            "inline-flex h-8 items-center gap-1 rounded-[var(--radius)] bg-[var(--color-accent-teal)] px-3 text-xs font-semibold text-[var(--color-bg-inset)]",
87
            "transition-[background,box-shadow] duration-[var(--duration-fast)] hover:bg-[var(--color-accent-teal-hover)] hover:shadow-[var(--shadow-glow-teal)]",
88
            "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent-teal)]",
89
          )}
90
          aria-label={`Buy tickets for ${event.title}`}
91
        >
92
          Buy <ArrowUpRight className="h-3.5 w-3.5" aria-hidden />
93
        </a>
94
      </div>
95
    </motion.article>
96
  );
97
}