From 18fbdeca372996e37a58cf79b4d07b8c6afd7e75 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 8 Dec 2025 00:34:57 +0900 Subject: refactor(client): replace inline SVGs with Font Awesome icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate all 28 inline SVG icons across 8 components to use Font Awesome React components for better maintainability and consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- package.json | 3 + pnpm-lock.yaml | 43 ++++++++ src/client/components/OfflineBanner.tsx | 17 +-- src/client/components/SyncButton.tsx | 38 ++----- src/client/components/SyncStatusIndicator.tsx | 77 ++++---------- src/client/pages/DeckDetailPage.tsx | 144 +++++++------------------- src/client/pages/HomePage.tsx | 106 +++++-------------- src/client/pages/LoginPage.tsx | 25 ++--- src/client/pages/NotFoundPage.tsx | 32 ++---- src/client/pages/StudyPage.tsx | 75 ++++---------- 10 files changed, 178 insertions(+), 382 deletions(-) diff --git a/package.json b/package.json index d462eb4..8b40aa2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,9 @@ "packageManager": "pnpm@10.23.0", "type": "module", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^7.1.0", + "@fortawesome/free-solid-svg-icons": "^7.1.0", + "@fortawesome/react-fontawesome": "^3.1.1", "@hono/node-server": "^1.19.6", "@hono/zod-validator": "^0.7.5", "argon2": "^0.44.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d200dd..5b97aae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@fortawesome/fontawesome-svg-core': + specifier: ^7.1.0 + version: 7.1.0 + '@fortawesome/free-solid-svg-icons': + specifier: ^7.1.0 + version: 7.1.0 + '@fortawesome/react-fontawesome': + specifier: ^3.1.1 + version: 3.1.1(@fortawesome/fontawesome-svg-core@7.1.0)(react@19.2.1) '@hono/node-server': specifier: ^1.19.6 version: 1.19.6(hono@4.10.7) @@ -1179,6 +1188,25 @@ packages: cpu: [x64] os: [win32] + '@fortawesome/fontawesome-common-types@7.1.0': + resolution: {integrity: sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@7.1.0': + resolution: {integrity: sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@7.1.0': + resolution: {integrity: sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==} + engines: {node: '>=6'} + + '@fortawesome/react-fontawesome@3.1.1': + resolution: {integrity: sha512-EDllr9hpodc21odmUywHS1alXNiCd4E8sp5GJ5s7wYINz8vSmMiNWpALTiuYODb865YyQ/NlyiN4mbXp7HCNqg==} + engines: {node: '>=20'} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~6 || ~7 + react: ^18.0.0 || ^19.0.0 + '@hono/cli@0.1.3': resolution: {integrity: sha512-jqIoJyCXKxCR6kd2Grxg9hRczju39r3xVu5dK4FG5wVxe257Bh09Qhw8pasY12VE/l46gL0sQG/XPgy+rn9yPA==} hasBin: true @@ -4161,6 +4189,21 @@ snapshots: '@esbuild/win32-x64@0.27.1': optional: true + '@fortawesome/fontawesome-common-types@7.1.0': {} + + '@fortawesome/fontawesome-svg-core@7.1.0': + dependencies: + '@fortawesome/fontawesome-common-types': 7.1.0 + + '@fortawesome/free-solid-svg-icons@7.1.0': + dependencies: + '@fortawesome/fontawesome-common-types': 7.1.0 + + '@fortawesome/react-fontawesome@3.1.1(@fortawesome/fontawesome-svg-core@7.1.0)(react@19.2.1)': + dependencies: + '@fortawesome/fontawesome-svg-core': 7.1.0 + react: 19.2.1 + '@hono/cli@0.1.3': dependencies: '@hono/node-server': 1.19.6(hono@4.10.7) diff --git a/src/client/components/OfflineBanner.tsx b/src/client/components/OfflineBanner.tsx index bf94908..b33fc14 100644 --- a/src/client/components/OfflineBanner.tsx +++ b/src/client/components/OfflineBanner.tsx @@ -1,3 +1,5 @@ +import { faWifi } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useSync } from "../stores"; export function OfflineBanner() { @@ -13,20 +15,11 @@ export function OfflineBanner() { aria-live="polite" className="bg-slate text-white py-2 px-4 text-sm flex items-center justify-center gap-2" > - + /> You're offline. Changes will sync when you reconnect. {pendingCount > 0 && ( diff --git a/src/client/components/SyncButton.tsx b/src/client/components/SyncButton.tsx index 82a6c68..1c214ad 100644 --- a/src/client/components/SyncButton.tsx +++ b/src/client/components/SyncButton.tsx @@ -1,3 +1,5 @@ +import { faArrowsRotate, faSpinner } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useSync } from "../stores"; export function SyncButton() { @@ -24,44 +26,20 @@ export function SyncButton() { > {isSyncing ? ( <> - + /> Syncing... ) : ( <> - + /> Sync )} diff --git a/src/client/components/SyncStatusIndicator.tsx b/src/client/components/SyncStatusIndicator.tsx index 0f555ca..dd1a77d 100644 --- a/src/client/components/SyncStatusIndicator.tsx +++ b/src/client/components/SyncStatusIndicator.tsx @@ -1,3 +1,11 @@ +import { + faCircle, + faCircleCheck, + faCircleXmark, + faClock, + faSpinner, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useSync } from "../stores"; import { SyncStatus } from "../sync"; @@ -39,85 +47,46 @@ export function SyncStatusIndicator() { const getStatusIcon = () => { if (!isOnline) { return ( - + /> ); } if (isSyncing) { return ( - + /> ); } if (status === SyncStatus.Error) { return ( - + /> ); } if (pendingCount > 0) { return ( - + /> ); } return ( - + /> ); }; diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx index cb1e3fb..5a3c14e 100644 --- a/src/client/pages/DeckDetailPage.tsx +++ b/src/client/pages/DeckDetailPage.tsx @@ -1,3 +1,13 @@ +import { + faChevronLeft, + faCirclePlay, + faFile, + faPen, + faPlus, + faSpinner, + faTrash, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useCallback, useEffect, useState } from "react"; import { Link, useParams } from "wouter"; import { ApiClientError, apiClient } from "../api"; @@ -144,20 +154,11 @@ export function DeckDetailPage() { href="/" className="inline-flex items-center gap-2 text-muted hover:text-slate transition-colors text-sm" > - + /> Back to Decks @@ -168,26 +169,11 @@ export function DeckDetailPage() { {/* Loading State */} {isLoading && (
- + />
)} @@ -227,26 +213,11 @@ export function DeckDetailPage() { href={`/decks/${deckId}/study`} className="inline-flex items-center gap-2 bg-success hover:bg-success/90 text-white font-medium py-3 px-6 rounded-xl transition-all duration-200 active:scale-[0.98] shadow-sm hover:shadow-md" > - + /> Study Now @@ -262,20 +233,11 @@ export function DeckDetailPage() { onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 active:scale-[0.98]" > - + /> Add Card @@ -284,20 +246,11 @@ export function DeckDetailPage() { {cards.length === 0 && (
- + />

No cards yet @@ -310,20 +263,11 @@ export function DeckDetailPage() { onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-2 px-4 rounded-lg transition-all duration-200" > - + /> Add Your First Card

@@ -386,20 +330,11 @@ export function DeckDetailPage() { className="p-2 text-muted hover:text-slate hover:bg-ivory rounded-lg transition-colors" title="Edit card" > - + /> diff --git a/src/client/pages/HomePage.tsx b/src/client/pages/HomePage.tsx index fcae971..b7b2c29 100644 --- a/src/client/pages/HomePage.tsx +++ b/src/client/pages/HomePage.tsx @@ -1,3 +1,11 @@ +import { + faBoxOpen, + faPen, + faPlus, + faSpinner, + faTrash, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useCallback, useEffect, useState } from "react"; import { Link } from "wouter"; import { ApiClientError, apiClient } from "../api"; @@ -95,20 +103,11 @@ export function HomePage() { onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-2 px-4 rounded-lg transition-all duration-200 active:scale-[0.98] shadow-sm hover:shadow-md" > - + /> New Deck @@ -116,26 +115,11 @@ export function HomePage() { {/* Loading State */} {isLoading && (
- + />
)} @@ -160,20 +144,11 @@ export function HomePage() { {!isLoading && !error && decks.length === 0 && (
- + />

No decks yet @@ -186,20 +161,11 @@ export function HomePage() { onClick={() => setIsCreateModalOpen(true)} className="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-2.5 px-5 rounded-lg transition-all duration-200" > - + /> Create Your First Deck

@@ -237,20 +203,11 @@ export function HomePage() { className="p-2 text-muted hover:text-slate hover:bg-ivory rounded-lg transition-colors" title="Edit deck" > - + /> diff --git a/src/client/pages/LoginPage.tsx b/src/client/pages/LoginPage.tsx index 89dd053..835c73e 100644 --- a/src/client/pages/LoginPage.tsx +++ b/src/client/pages/LoginPage.tsx @@ -1,3 +1,5 @@ +import { faSpinner } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { type FormEvent, useEffect, useState } from "react"; import { useLocation } from "wouter"; import { ApiClientError, useAuth } from "../stores"; @@ -111,26 +113,11 @@ export function LoginPage() { > {isSubmitting ? ( - + /> Signing in... ) : ( diff --git a/src/client/pages/NotFoundPage.tsx b/src/client/pages/NotFoundPage.tsx index 72531c1..c94340e 100644 --- a/src/client/pages/NotFoundPage.tsx +++ b/src/client/pages/NotFoundPage.tsx @@ -1,3 +1,5 @@ +import { faFaceSadTear, faHouse } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link } from "wouter"; export function NotFoundPage() { @@ -5,20 +7,11 @@ export function NotFoundPage() {
- + />

404

@@ -31,20 +24,11 @@ export function NotFoundPage() { href="/" className="inline-flex items-center gap-2 bg-primary hover:bg-primary-dark text-white font-medium py-2.5 px-5 rounded-lg transition-all duration-200" > - + /> Go Home

diff --git a/src/client/pages/StudyPage.tsx b/src/client/pages/StudyPage.tsx index 16c1a1c..5bd31c0 100644 --- a/src/client/pages/StudyPage.tsx +++ b/src/client/pages/StudyPage.tsx @@ -1,3 +1,10 @@ +import { + faCheck, + faChevronLeft, + faCircleCheck, + faSpinner, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useCallback, useEffect, useRef, useState } from "react"; import { Link, useParams } from "wouter"; import { ApiClientError, apiClient } from "../api"; @@ -245,20 +252,11 @@ export function StudyPage() { href={`/decks/${deckId}`} className="inline-flex items-center gap-2 text-muted hover:text-slate transition-colors text-sm" > - + /> Back to Deck
@@ -269,26 +267,11 @@ export function StudyPage() { {/* Loading State */} {isLoading && (
- + />
)} @@ -335,20 +318,11 @@ export function StudyPage() { >
- + />

All caught up! @@ -374,20 +348,11 @@ export function StudyPage() { >
- + />

Session Complete! -- cgit v1.2.3-70-g09d2