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 type { ReactNode } from "react";
import { LucideIcon } from "../lib/iconData";
interface DashboardCardProps {
to: string;
search?: Record<string, string>;
title: string;
icon: ReactNode;
icon: string;
stats: Array<{ label: string; value: 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"
>
<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>
</div>
<div className="space-y-1.5">

View File

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

View File

@@ -10,6 +10,7 @@ import { useCategories } from "../../hooks/useCategories";
import { useItems } from "../../hooks/useItems";
import { useThreads } from "../../hooks/useThreads";
import { useTotals } from "../../hooks/useTotals";
import { LucideIcon } from "../../lib/iconData";
import { useUIStore } from "../../stores/uiStore";
const searchSchema = z.object({
@@ -61,7 +62,13 @@ function CollectionView() {
return (
<div className="py-16 text-center">
<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">
Your collection is empty
</h2>
@@ -158,6 +165,7 @@ function CollectionView() {
categoryName={categoryName}
categoryIcon={categoryIcon}
imageFilename={item.imageFilename}
productUrl={item.productUrl}
/>
))}
</div>

View File

@@ -1,9 +1,9 @@
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 { 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("/")({
component: DashboardPage,
@@ -24,10 +24,13 @@ function DashboardPage() {
<DashboardCard
to="/collection"
title="Collection"
icon="🎒"
icon="backpack"
stats={[
{ 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) },
]}
emptyText="Get started"
@@ -36,7 +39,7 @@ function DashboardPage() {
to="/collection"
search={{ tab: "planning" }}
title="Planning"
icon="🔍"
icon="search"
stats={[
{ label: "Active threads", value: String(activeThreadCount) },
]}
@@ -44,10 +47,8 @@ function DashboardPage() {
<DashboardCard
to="/setups"
title="Setups"
icon="🏕️"
stats={[
{ label: "Setups", value: String(setupCount) },
]}
icon="tent"
stats={[{ label: "Setups", value: String(setupCount) }]}
/>
</div>
</div>

View File

@@ -1,14 +1,15 @@
import { useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import {
useSetup,
useDeleteSetup,
useRemoveSetupItem,
} from "../../hooks/useSetups";
import { useState } from "react";
import { CategoryHeader } from "../../components/CategoryHeader";
import { ItemCard } from "../../components/ItemCard";
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")({
component: SetupDetailPage,
@@ -153,7 +154,13 @@ function SetupDetailPage() {
{itemCount === 0 && (
<div className="py-16 text-center">
<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">
No items in this setup
</h2>
@@ -208,6 +215,7 @@ function SetupDetailPage() {
categoryName={categoryName}
categoryIcon={categoryIcon}
imageFilename={item.imageFilename}
productUrl={item.productUrl}
onRemove={() => removeItem.mutate(item.id)}
/>
))}

View File

@@ -1,7 +1,8 @@
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { useSetups, useCreateSetup } from "../../hooks/useSetups";
import { useState } from "react";
import { SetupCard } from "../../components/SetupCard";
import { useCreateSetup, useSetups } from "../../hooks/useSetups";
import { LucideIcon } from "../../lib/iconData";
export const Route = createFileRoute("/setups/")({
component: SetupsListPage,
@@ -16,10 +17,7 @@ function SetupsListPage() {
e.preventDefault();
const name = newSetupName.trim();
if (!name) return;
createSetup.mutate(
{ name },
{ onSuccess: () => setNewSetupName("") },
);
createSetup.mutate({ name }, { onSuccess: () => setNewSetupName("") });
}
return (
@@ -46,7 +44,10 @@ function SetupsListPage() {
{isLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[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>
)}
@@ -55,7 +56,13 @@ function SetupsListPage() {
{!isLoading && (!setups || setups.length === 0) && (
<div className="py-16 text-center">
<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">
No setups yet
</h2>

View File

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