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:
@@ -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">
|
||||||
|
|||||||
@@ -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">🎉</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're all set!
|
You'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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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() {
|
|||||||
← Back to planning
|
← 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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user