fix: react-doctor unused-file 123→27 (-96 dead files removed)

This commit is contained in:
Nora
2026-06-26 04:38:44 -06:00
parent ab9813b4ee
commit 27b2ded94e
96 changed files with 0 additions and 17319 deletions
@@ -1,98 +0,0 @@
"use client";
import Link from "next/link";
export type BreadcrumbItem = {
label: string;
href?: string;
};
export type BreadcrumbNavProps = {
/** Array of breadcrumb items. Last item is the current page (no href). */
items: BreadcrumbItem[];
/** Optional brand accent for styling: 'green' (Tuxedo) or 'blue' (Indian River) */
brandAccent?: "green" | "blue";
/** Optional additional CSS classes */
className?: string;
};
/**
* Breadcrumb navigation component following Apple HIG.
* Uses semantic <nav> with aria-label and ordered list for accessibility.
*/
export default function BreadcrumbNav({
items,
brandAccent = "green",
className = "",
}: BreadcrumbNavProps) {
if (!items || items.length === 0) {
return null;
}
const accentColor = brandAccent === "blue"
? "text-blue-600 hover:text-blue-700"
: "text-emerald-600 hover:text-emerald-700";
const separatorColor = "text-stone-400";
return (
<nav
aria-label="Breadcrumb"
className={`w-full ${className}`}
>
<ol className="flex items-center flex-wrap gap-2 text-sm">
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<li
key={`${item.label}-${item.href ?? ""}`}
className="flex items-center"
>
{index > 0 && (
<span
className={`mx-2 ${separatorColor}`}
aria-hidden="true"
>
<svg
className="h-3.5 w-3.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.25 4.5l7.5 7.5-7.5 7.5"
/>
</svg>
</span>
)}
{isLast ? (
<span
className="font-medium text-stone-700"
aria-current="page"
>
{item.label}
</span>
) : item.href ? (
<Link
href={item.href}
className={`font-medium transition-colors ${accentColor}`}
>
{item.label}
</Link>
) : (
<span className={`font-medium ${accentColor}`}>
{item.label}
</span>
)}
</li>
);
})}
</ol>
</nav>
);
}
@@ -1,21 +0,0 @@
"use client";
/**
* IndianRiverGrainOverlay
*
* Atmospheric grain texture component for Indian River Direct storefront.
* Provides the subtle film-grain texture that creates a premium, organic feel
* reminiscent of high-end agricultural photography.
*
* Uses a blue/emerald color scheme to complement the citrus brand identity.
*/
export default function IndianRiverGrainOverlay() {
return (
<div
className="fixed inset-0 pointer-events-none z-50 opacity-[0.03]"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
}}
/>
);
}
@@ -1,179 +0,0 @@
"use client";
import { useState, useMemo } from "react";
import StopCard from "./StopCard";
type Stop = {
id: string;
city: string;
state: string;
date: string;
time: string;
location: string;
slug: string;
brand_id: string;
};
type Props = {
stops: Stop[];
brandSlug: string;
};
const THIS_WEEK_LIMIT = 10;
const TWO_WEEKS_LIMIT = 10;
const FULL_SEASON_PAGE_SIZE = 20;
type Tab = "this_week" | "next_two_weeks" | "full_season";
function getDateRange(tab: Tab): { start: Date; end: Date } {
const today = new Date();
today.setHours(0, 0, 0, 0);
const addDays = (d: Date, n: number) => {
const copy = new Date(d);
copy.setDate(copy.getDate() + n);
return copy;
};
if (tab === "this_week") return { start: today, end: addDays(new Date(today), 7) };
if (tab === "next_two_weeks") return { start: today, end: addDays(new Date(today), 14) };
return { start: new Date(0), end: new Date("2100-01-01") };
}
export default function IndianRiverPaginatedStops({ stops, brandSlug }: Props) {
const [activeTab, setActiveTab] = useState<Tab>("this_week");
const [fullSeasonPage, setFullSeasonPage] = useState(0);
const [fullSeasonSearch, setFullSeasonSearch] = useState("");
const { start, end } = getDateRange(activeTab);
const filtered = useMemo(() => {
if (activeTab === "full_season") {
if (!fullSeasonSearch.trim()) return stops;
const q = fullSeasonSearch.toLowerCase();
return stops.filter(
(s) =>
s.city.toLowerCase().includes(q) ||
s.location.toLowerCase().includes(q) ||
s.state.toLowerCase().includes(q)
);
}
return stops.filter((s) => {
const d = new Date(s.date);
return d >= start && d <= end;
});
}, [activeTab, stops, start, end, fullSeasonSearch]);
const paginatedStops = useMemo(() => {
if (activeTab === "this_week") return filtered.slice(0, THIS_WEEK_LIMIT);
if (activeTab === "next_two_weeks") return filtered.slice(0, TWO_WEEKS_LIMIT);
const offset = fullSeasonPage * FULL_SEASON_PAGE_SIZE;
return filtered.slice(offset, offset + FULL_SEASON_PAGE_SIZE);
}, [activeTab, filtered, fullSeasonPage]);
const totalCount = filtered.length;
const totalStops = stops.length;
const totalPages = Math.ceil(totalCount / FULL_SEASON_PAGE_SIZE);
const handleTabChange = (tab: Tab) => {
setActiveTab(tab);
setFullSeasonPage(0);
setFullSeasonSearch("");
};
return (
<div>
{/* Header row */}
<div className="mb-8 flex items-center justify-between">
<p className="text-sm text-stone-400 font-medium">
{totalStops} stop{totalStops !== 1 ? "s" : ""} this season
</p>
</div>
{/* Tabs */}
<div className="mb-10 flex gap-2 flex-wrap">
{(["this_week", "next_two_weeks", "full_season"] as Tab[]).map((tab) => (
<button type="button"
key={tab}
onClick={() => handleTabChange(tab)}
className={`rounded-2xl px-5 py-3 text-sm font-semibold transition-colors ${
activeTab === tab
? "bg-blue-600 text-white"
: "bg-white text-stone-500 ring-1 ring-stone-200 hover:bg-stone-50"
}`}
>
{tab === "this_week"
? "This Week"
: tab === "next_two_weeks"
? "Next 2 Weeks"
: "Full Season"}
</button>
))}
</div>
{/* Search — full season */}
{activeTab === "full_season" && (
<div className="mb-10 flex items-center gap-4 flex-col sm:flex-row">
<input aria-label="Search By City, Location, Or State..."
type="text"
placeholder="Search by city, location, or state..."
value={fullSeasonSearch}
onChange={(e) => {
setFullSeasonSearch(e.target.value);
setFullSeasonPage(0);
}}
className="flex-1 rounded-2xl border border-stone-200 bg-white px-5 py-3.5 text-sm text-stone-900 placeholder-stone-400 outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"
/>
<span className="text-sm text-stone-500 whitespace-nowrap">
{totalCount > 0
? `${paginatedStops.length} of ${totalCount} stops`
: `${totalCount} stops`}
</span>
</div>
)}
{/* Empty state */}
{filtered.length === 0 ? (
<div className="rounded-3xl bg-white p-14 text-center ring-1 ring-stone-200/60">
<p className="text-stone-500">No stops found for this period.</p>
</div>
) : (
<>
<div className="grid gap-6 md:grid-cols-3">
{paginatedStops.map((stop) => (
<StopCard
key={stop.id}
{...stop}
brandSlug={brandSlug}
brandAccent="blue"
/>
))}
</div>
{/* Pagination */}
{activeTab === "full_season" && totalPages > 1 && (
<div className="mt-10 flex items-center justify-between flex-col sm:flex-row gap-6">
<span className="text-sm text-stone-500">
Page {fullSeasonPage + 1} of {totalPages}
</span>
<div className="flex gap-3">
<button type="button"
onClick={() => setFullSeasonPage((p) => Math.max(0, p - 1))}
disabled={fullSeasonPage === 0}
className="rounded-2xl border border-stone-300 px-5 py-2.5 text-sm font-semibold text-stone-600 disabled:opacity-40 hover:bg-stone-50 transition-colors"
>
Previous
</button>
<button type="button"
onClick={() => setFullSeasonPage((p) => p + 1)}
disabled={fullSeasonPage >= totalPages - 1}
className="rounded-2xl border border-stone-300 px-5 py-2.5 text-sm font-semibold text-stone-600 disabled:opacity-40 hover:bg-stone-50 transition-colors"
>
Next
</button>
</div>
</div>
)}
</>
)}
</div>
);
}
-120
View File
@@ -1,120 +0,0 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { formatDate } from "@/lib/format-date";
type ZipCodeSearchProps = {
allStops?: Array<{
id: string;
city: string;
state: string;
date: string;
time: string;
location: string;
slug: string;
}>;
brandAccent?: "green" | "orange" | "blue";
};
export default function ZipCodeSearch({ allStops = [], brandAccent = "blue" }: ZipCodeSearchProps) {
const router = useRouter();
const [zipCode, setZipCode] = useState("");
const [filteredStops, setFilteredStops] = useState<typeof allStops>([]);
const [searched, setSearched] = useState(false);
const isBlue = brandAccent === "blue";
return (
<div className="rounded-3xl bg-white p-8 shadow-sm ring-1 ring-stone-200/60">
{/* Header */}
<div className="flex items-center gap-4 mb-8">
<div className={`flex h-12 w-12 items-center justify-center rounded-2xl ${isBlue ? "bg-blue-50" : "bg-stone-100"}`}>
<svg className={`h-5 w-5 ${isBlue ? "text-blue-500" : "text-stone-500"}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div>
<h2 className="text-2xl font-bold tracking-tight text-stone-950">Find Stops Near You</h2>
<p className="text-sm text-stone-500 mt-0.5">Search by ZIP code or city name</p>
</div>
</div>
{/* Search row */}
<div className="flex gap-3">
<div className="relative flex-1">
<div className="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
<svg className="h-4 w-4 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input aria-label="ZIP Code Or City..."
type="text"
placeholder="ZIP code or city..."
value={zipCode}
onChange={(e) => setZipCode(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") { if (!zipCode.trim()) return; const normalized = zipCode.trim().replace(/\D/g, ""); setFilteredStops(allStops.filter((s) => s.city.toLowerCase().includes(normalized) || s.location.toLowerCase().includes(normalized) || s.state.toLowerCase().includes(normalized.toUpperCase()))); setSearched(true); } }}
className="w-full rounded-xl border border-stone-200 pl-11 pr-4 py-3.5 text-sm text-stone-900 placeholder-stone-400 bg-white outline-none focus:border-stone-900 focus:ring-1 focus:ring-stone-900 transition-colors shadow-sm"
/>
</div>
<button type="button"
onClick={() => {
if (!zipCode.trim()) return;
const normalized = zipCode.trim().replace(/\D/g, "");
setFilteredStops(allStops.filter((s) =>
s.city.toLowerCase().includes(normalized) ||
s.location.toLowerCase().includes(normalized) ||
s.state.toLowerCase().includes(normalized.toUpperCase())
));
setSearched(true);
}}
className="rounded-xl bg-stone-900 px-7 py-3.5 text-sm font-semibold text-white hover:bg-stone-800 active:bg-stone-950 transition-all hover:-translate-y-0.5 shadow-sm"
>
Search
</button>
</div>
{/* Results */}
{searched && (
<div className="mt-6 pt-6 border-t border-stone-100">
{filteredStops.length > 0 ? (
<div className="space-y-3">
<p className="text-sm font-semibold text-stone-700">
{filteredStops.length} stop{filteredStops.length !== 1 ? "s" : ""} found
</p>
{filteredStops.map((stop) => (
<button type="button"
key={stop.id}
onClick={() => router.push(`#stops`)}
className="w-full rounded-xl border border-stone-200 bg-stone-50 px-5 py-4 text-left hover:bg-stone-100 group transition-all"
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="font-semibold text-base text-stone-950">{stop.city}, {stop.state}</p>
<p className="mt-1 text-sm text-stone-500">{formatDate(stop.date)} &middot; {stop.time} &middot; {stop.location}</p>
</div>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-stone-200/60 mt-0.5">
<svg className="h-4 w-4 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</button>
))}
</div>
) : (
<div className="text-center py-8">
<div className="inline-flex h-14 w-14 items-center justify-center rounded-full bg-stone-100 mb-4">
<svg className="h-6 w-6 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<p className="font-medium text-stone-600">No stops found for that location.</p>
<p className="text-sm mt-1 text-stone-400">Try a nearby ZIP code or browse all stops below.</p>
</div>
)}
</div>
)}
</div>
);
}