refactor: replace remaining emojis with Lucide icons

Replace all raw emoji characters in dashboard cards, empty states,
and onboarding wizard with LucideIcon components for visual consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 18:47:50 +01:00
parent 407fa45280
commit 7c3740fc72
7 changed files with 853 additions and 818 deletions

View File

@@ -1,11 +1,11 @@
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import type { ReactNode } from "react"; import { LucideIcon } from "../lib/iconData";
interface DashboardCardProps { interface DashboardCardProps {
to: string; to: string;
search?: Record<string, string>; search?: Record<string, string>;
title: string; title: string;
icon: ReactNode; icon: string;
stats: Array<{ label: string; value: string }>; stats: Array<{ label: string; value: string }>;
emptyText?: string; emptyText?: string;
} }
@@ -29,7 +29,7 @@ export function DashboardCard({
className="block bg-white rounded-xl border border-gray-100 hover:border-gray-200 hover:shadow-md transition-all p-6" className="block bg-white rounded-xl border border-gray-100 hover:border-gray-200 hover:shadow-md transition-all p-6"
> >
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<span className="text-2xl">{icon}</span> <LucideIcon name={icon} size={24} className="text-gray-500" />
<h2 className="text-lg font-semibold text-gray-900">{title}</h2> <h2 className="text-lg font-semibold text-gray-900">{title}</h2>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">

View File

@@ -2,6 +2,7 @@ import { useState } from "react";
import { useCreateCategory } from "../hooks/useCategories"; import { useCreateCategory } from "../hooks/useCategories";
import { useCreateItem } from "../hooks/useItems"; import { useCreateItem } from "../hooks/useItems";
import { useUpdateSetting } from "../hooks/useSettings"; import { useUpdateSetting } from "../hooks/useSettings";
import { LucideIcon } from "../lib/iconData";
import { IconPicker } from "./IconPicker"; import { IconPicker } from "./IconPicker";
interface OnboardingWizardProps { interface OnboardingWizardProps {
@@ -15,7 +16,9 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
const [categoryName, setCategoryName] = useState(""); const [categoryName, setCategoryName] = useState("");
const [categoryIcon, setCategoryIcon] = useState(""); const [categoryIcon, setCategoryIcon] = useState("");
const [categoryError, setCategoryError] = useState(""); const [categoryError, setCategoryError] = useState("");
const [createdCategoryId, setCreatedCategoryId] = useState<number | null>(null); const [createdCategoryId, setCreatedCategoryId] = useState<number | null>(
null,
);
// Step 3 state // Step 3 state
const [itemName, setItemName] = useState(""); const [itemName, setItemName] = useState("");
@@ -99,9 +102,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
<div <div
key={s} key={s}
className={`h-1.5 rounded-full transition-all ${ className={`h-1.5 rounded-full transition-all ${
s <= Math.min(step, 3) s <= Math.min(step, 3) ? "bg-blue-600 w-8" : "bg-gray-200 w-6"
? "bg-blue-600 w-8"
: "bg-gray-200 w-6"
}`} }`}
/> />
))} ))}
@@ -266,9 +267,7 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
</div> </div>
</div> </div>
{itemError && ( {itemError && <p className="text-xs text-red-500">{itemError}</p>}
<p className="text-xs text-red-500">{itemError}</p>
)}
</div> </div>
<button <button
@@ -292,13 +291,19 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
{/* Step 4: Done */} {/* Step 4: Done */}
{step === 4 && ( {step === 4 && (
<div className="text-center"> <div className="text-center">
<div className="text-4xl mb-4">&#127881;</div> <div className="mb-4">
<LucideIcon
name="party-popper"
size={48}
className="text-gray-400 mx-auto"
/>
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"> <h2 className="text-xl font-semibold text-gray-900 mb-2">
You&apos;re all set! You&apos;re all set!
</h2> </h2>
<p className="text-sm text-gray-500 mb-8"> <p className="text-sm text-gray-500 mb-8">
Your first item has been added. You can now browse your collection, Your first item has been added. You can now browse your
add more gear, and track your setup. collection, add more gear, and track your setup.
</p> </p>
<button <button
type="button" type="button"

View File

@@ -10,6 +10,7 @@ import { useCategories } from "../../hooks/useCategories";
import { useItems } from "../../hooks/useItems"; import { useItems } from "../../hooks/useItems";
import { useThreads } from "../../hooks/useThreads"; import { useThreads } from "../../hooks/useThreads";
import { useTotals } from "../../hooks/useTotals"; import { useTotals } from "../../hooks/useTotals";
import { LucideIcon } from "../../lib/iconData";
import { useUIStore } from "../../stores/uiStore"; import { useUIStore } from "../../stores/uiStore";
const searchSchema = z.object({ const searchSchema = z.object({
@@ -61,7 +62,13 @@ function CollectionView() {
return ( return (
<div className="py-16 text-center"> <div className="py-16 text-center">
<div className="max-w-md mx-auto"> <div className="max-w-md mx-auto">
<div className="text-5xl mb-4">🎒</div> <div className="mb-4">
<LucideIcon
name="backpack"
size={48}
className="text-gray-400 mx-auto"
/>
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"> <h2 className="text-xl font-semibold text-gray-900 mb-2">
Your collection is empty Your collection is empty
</h2> </h2>
@@ -158,6 +165,7 @@ function CollectionView() {
categoryName={categoryName} categoryName={categoryName}
categoryIcon={categoryIcon} categoryIcon={categoryIcon}
imageFilename={item.imageFilename} imageFilename={item.imageFilename}
productUrl={item.productUrl}
/> />
))} ))}
</div> </div>

View File

@@ -1,9 +1,9 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { useTotals } from "../hooks/useTotals";
import { useThreads } from "../hooks/useThreads";
import { useSetups } from "../hooks/useSetups";
import { DashboardCard } from "../components/DashboardCard"; import { DashboardCard } from "../components/DashboardCard";
import { formatWeight, formatPrice } from "../lib/formatters"; import { useSetups } from "../hooks/useSetups";
import { useThreads } from "../hooks/useThreads";
import { useTotals } from "../hooks/useTotals";
import { formatPrice, formatWeight } from "../lib/formatters";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: DashboardPage, component: DashboardPage,
@@ -24,10 +24,13 @@ function DashboardPage() {
<DashboardCard <DashboardCard
to="/collection" to="/collection"
title="Collection" title="Collection"
icon="🎒" icon="backpack"
stats={[ stats={[
{ label: "Items", value: String(global?.itemCount ?? 0) }, { label: "Items", value: String(global?.itemCount ?? 0) },
{ label: "Weight", value: formatWeight(global?.totalWeight ?? null) }, {
label: "Weight",
value: formatWeight(global?.totalWeight ?? null),
},
{ label: "Cost", value: formatPrice(global?.totalCost ?? null) }, { label: "Cost", value: formatPrice(global?.totalCost ?? null) },
]} ]}
emptyText="Get started" emptyText="Get started"
@@ -36,7 +39,7 @@ function DashboardPage() {
to="/collection" to="/collection"
search={{ tab: "planning" }} search={{ tab: "planning" }}
title="Planning" title="Planning"
icon="🔍" icon="search"
stats={[ stats={[
{ label: "Active threads", value: String(activeThreadCount) }, { label: "Active threads", value: String(activeThreadCount) },
]} ]}
@@ -44,10 +47,8 @@ function DashboardPage() {
<DashboardCard <DashboardCard
to="/setups" to="/setups"
title="Setups" title="Setups"
icon="🏕️" icon="tent"
stats={[ stats={[{ label: "Setups", value: String(setupCount) }]}
{ label: "Setups", value: String(setupCount) },
]}
/> />
</div> </div>
</div> </div>

View File

@@ -1,14 +1,15 @@
import { useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { import { useState } from "react";
useSetup,
useDeleteSetup,
useRemoveSetupItem,
} from "../../hooks/useSetups";
import { CategoryHeader } from "../../components/CategoryHeader"; import { CategoryHeader } from "../../components/CategoryHeader";
import { ItemCard } from "../../components/ItemCard"; import { ItemCard } from "../../components/ItemCard";
import { ItemPicker } from "../../components/ItemPicker"; import { ItemPicker } from "../../components/ItemPicker";
import { formatWeight, formatPrice } from "../../lib/formatters"; import {
useDeleteSetup,
useRemoveSetupItem,
useSetup,
} from "../../hooks/useSetups";
import { formatPrice, formatWeight } from "../../lib/formatters";
import { LucideIcon } from "../../lib/iconData";
export const Route = createFileRoute("/setups/$setupId")({ export const Route = createFileRoute("/setups/$setupId")({
component: SetupDetailPage, component: SetupDetailPage,
@@ -153,7 +154,13 @@ function SetupDetailPage() {
{itemCount === 0 && ( {itemCount === 0 && (
<div className="py-16 text-center"> <div className="py-16 text-center">
<div className="max-w-md mx-auto"> <div className="max-w-md mx-auto">
<div className="text-5xl mb-4">📦</div> <div className="mb-4">
<LucideIcon
name="package"
size={48}
className="text-gray-400 mx-auto"
/>
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"> <h2 className="text-xl font-semibold text-gray-900 mb-2">
No items in this setup No items in this setup
</h2> </h2>
@@ -208,6 +215,7 @@ function SetupDetailPage() {
categoryName={categoryName} categoryName={categoryName}
categoryIcon={categoryIcon} categoryIcon={categoryIcon}
imageFilename={item.imageFilename} imageFilename={item.imageFilename}
productUrl={item.productUrl}
onRemove={() => removeItem.mutate(item.id)} onRemove={() => removeItem.mutate(item.id)}
/> />
))} ))}

View File

@@ -1,7 +1,8 @@
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { useSetups, useCreateSetup } from "../../hooks/useSetups"; import { useState } from "react";
import { SetupCard } from "../../components/SetupCard"; import { SetupCard } from "../../components/SetupCard";
import { useCreateSetup, useSetups } from "../../hooks/useSetups";
import { LucideIcon } from "../../lib/iconData";
export const Route = createFileRoute("/setups/")({ export const Route = createFileRoute("/setups/")({
component: SetupsListPage, component: SetupsListPage,
@@ -16,10 +17,7 @@ function SetupsListPage() {
e.preventDefault(); e.preventDefault();
const name = newSetupName.trim(); const name = newSetupName.trim();
if (!name) return; if (!name) return;
createSetup.mutate( createSetup.mutate({ name }, { onSuccess: () => setNewSetupName("") });
{ name },
{ onSuccess: () => setNewSetupName("") },
);
} }
return ( return (
@@ -46,7 +44,10 @@ function SetupsListPage() {
{isLoading && ( {isLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[1, 2].map((i) => ( {[1, 2].map((i) => (
<div key={i} className="h-24 bg-gray-200 rounded-xl animate-pulse" /> <div
key={i}
className="h-24 bg-gray-200 rounded-xl animate-pulse"
/>
))} ))}
</div> </div>
)} )}
@@ -55,7 +56,13 @@ function SetupsListPage() {
{!isLoading && (!setups || setups.length === 0) && ( {!isLoading && (!setups || setups.length === 0) && (
<div className="py-16 text-center"> <div className="py-16 text-center">
<div className="max-w-md mx-auto"> <div className="max-w-md mx-auto">
<div className="text-5xl mb-4">🏕</div> <div className="mb-4">
<LucideIcon
name="tent"
size={48}
className="text-gray-400 mx-auto"
/>
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"> <h2 className="text-xl font-semibold text-gray-900 mb-2">
No setups yet No setups yet
</h2> </h2>

View File

@@ -1,6 +1,7 @@
import { createFileRoute, Link } from "@tanstack/react-router"; import { createFileRoute, Link } from "@tanstack/react-router";
import { useThread } from "../../hooks/useThreads";
import { CandidateCard } from "../../components/CandidateCard"; import { CandidateCard } from "../../components/CandidateCard";
import { useThread } from "../../hooks/useThreads";
import { LucideIcon } from "../../lib/iconData";
import { useUIStore } from "../../stores/uiStore"; import { useUIStore } from "../../stores/uiStore";
export const Route = createFileRoute("/threads/$threadId")({ export const Route = createFileRoute("/threads/$threadId")({
@@ -62,9 +63,7 @@ function ThreadDetailPage() {
&larr; Back to planning &larr; Back to planning
</Link> </Link>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h1 className="text-xl font-semibold text-gray-900"> <h1 className="text-xl font-semibold text-gray-900">{thread.name}</h1>
{thread.name}
</h1>
<span <span
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${ className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
isActive isActive
@@ -116,7 +115,13 @@ function ThreadDetailPage() {
{/* Candidate grid */} {/* Candidate grid */}
{thread.candidates.length === 0 ? ( {thread.candidates.length === 0 ? (
<div className="py-12 text-center"> <div className="py-12 text-center">
<div className="text-4xl mb-3">🏷</div> <div className="mb-3">
<LucideIcon
name="tag"
size={48}
className="text-gray-400 mx-auto"
/>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-1"> <h3 className="text-lg font-semibold text-gray-900 mb-1">
No candidates yet No candidates yet
</h3> </h3>
@@ -136,6 +141,7 @@ function ThreadDetailPage() {
categoryName={candidate.categoryName} categoryName={candidate.categoryName}
categoryIcon={candidate.categoryIcon} categoryIcon={candidate.categoryIcon}
imageFilename={candidate.imageFilename} imageFilename={candidate.imageFilename}
productUrl={candidate.productUrl}
threadId={threadId} threadId={threadId}
isActive={isActive} isActive={isActive}
/> />