aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-08 00:34:57 +0900
committernsfisis <nsfisis@gmail.com>2025-12-08 00:34:57 +0900
commit18fbdeca372996e37a58cf79b4d07b8c6afd7e75 (patch)
tree97044c9f07a96c74e0dc91b515bfb231131b420d
parentc8681623f72c461243dcc08cb303ead97dbeda0c (diff)
downloadkioku-18fbdeca372996e37a58cf79b4d07b8c6afd7e75.tar.gz
kioku-18fbdeca372996e37a58cf79b4d07b8c6afd7e75.tar.zst
kioku-18fbdeca372996e37a58cf79b4d07b8c6afd7e75.zip
refactor(client): replace inline SVGs with Font Awesome icons
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 <noreply@anthropic.com>
-rw-r--r--package.json3
-rw-r--r--pnpm-lock.yaml43
-rw-r--r--src/client/components/OfflineBanner.tsx17
-rw-r--r--src/client/components/SyncButton.tsx38
-rw-r--r--src/client/components/SyncStatusIndicator.tsx77
-rw-r--r--src/client/pages/DeckDetailPage.tsx144
-rw-r--r--src/client/pages/HomePage.tsx106
-rw-r--r--src/client/pages/LoginPage.tsx25
-rw-r--r--src/client/pages/NotFoundPage.tsx32
-rw-r--r--src/client/pages/StudyPage.tsx75
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faWifi}
className="w-4 h-4 text-warning"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
- />
- </svg>
+ />
<span>
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 ? (
<>
- <svg
+ <FontAwesomeIcon
+ icon={faSpinner}
className="w-4 h-4 animate-spin"
- fill="none"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
<span>Syncing...</span>
</>
) : (
<>
- <svg
+ <FontAwesomeIcon
+ icon={faArrowsRotate}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
- />
- </svg>
+ />
<span>Sync</span>
</>
)}
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 (
- <svg
+ <FontAwesomeIcon
+ icon={faCircle}
className="w-3.5 h-3.5"
- fill="currentColor"
- viewBox="0 0 20 20"
aria-hidden="true"
- >
- <circle cx="10" cy="10" r="4" />
- </svg>
+ />
);
}
if (isSyncing) {
return (
- <svg
+ <FontAwesomeIcon
+ icon={faSpinner}
className="w-3.5 h-3.5 animate-spin"
- fill="none"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
);
}
if (status === SyncStatus.Error) {
return (
- <svg
+ <FontAwesomeIcon
+ icon={faCircleXmark}
className="w-3.5 h-3.5"
- fill="currentColor"
- viewBox="0 0 20 20"
aria-hidden="true"
- >
- <path
- fillRule="evenodd"
- d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
- clipRule="evenodd"
- />
- </svg>
+ />
);
}
if (pendingCount > 0) {
return (
- <svg
+ <FontAwesomeIcon
+ icon={faClock}
className="w-3.5 h-3.5"
- fill="currentColor"
- viewBox="0 0 20 20"
aria-hidden="true"
- >
- <path
- fillRule="evenodd"
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
- clipRule="evenodd"
- />
- </svg>
+ />
);
}
return (
- <svg
+ <FontAwesomeIcon
+ icon={faCircleCheck}
className="w-3.5 h-3.5"
- fill="currentColor"
- viewBox="0 0 20 20"
aria-hidden="true"
- >
- <path
- fillRule="evenodd"
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
- clipRule="evenodd"
- />
- </svg>
+ />
);
};
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faChevronLeft}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M15 19l-7-7 7-7"
- />
- </svg>
+ />
Back to Decks
</Link>
</div>
@@ -168,26 +169,11 @@ export function DeckDetailPage() {
{/* Loading State */}
{isLoading && (
<div className="flex items-center justify-center py-12">
- <svg
- className="animate-spin h-8 w-8 text-primary"
- viewBox="0 0 24 24"
+ <FontAwesomeIcon
+ icon={faSpinner}
+ className="h-8 w-8 text-primary animate-spin"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- fill="none"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
</div>
)}
@@ -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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faCirclePlay}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
- />
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
- />
- </svg>
+ />
Study Now
</Link>
</div>
@@ -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]"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPlus}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 4v16m8-8H4"
- />
- </svg>
+ />
Add Card
</button>
</div>
@@ -284,20 +246,11 @@ export function DeckDetailPage() {
{cards.length === 0 && (
<div className="text-center py-12 bg-white rounded-xl border border-border/50">
<div className="w-14 h-14 mx-auto mb-4 bg-ivory rounded-xl flex items-center justify-center">
- <svg
+ <FontAwesomeIcon
+ icon={faFile}
className="w-7 h-7 text-muted"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={1.5}
- d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
- />
- </svg>
+ />
</div>
<h3 className="font-display text-lg font-medium text-slate mb-2">
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPlus}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 4v16m8-8H4"
- />
- </svg>
+ />
Add Your First Card
</button>
</div>
@@ -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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPen}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
- />
- </svg>
+ />
</button>
<button
type="button"
@@ -407,20 +342,11 @@ export function DeckDetailPage() {
className="p-2 text-muted hover:text-error hover:bg-error/5 rounded-lg transition-colors"
title="Delete card"
>
- <svg
+ <FontAwesomeIcon
+ icon={faTrash}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
- />
- </svg>
+ />
</button>
</div>
</div>
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPlus}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 4v16m8-8H4"
- />
- </svg>
+ />
New Deck
</button>
</div>
@@ -116,26 +115,11 @@ export function HomePage() {
{/* Loading State */}
{isLoading && (
<div className="flex items-center justify-center py-12">
- <svg
- className="animate-spin h-8 w-8 text-primary"
- viewBox="0 0 24 24"
+ <FontAwesomeIcon
+ icon={faSpinner}
+ className="h-8 w-8 text-primary animate-spin"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- fill="none"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
</div>
)}
@@ -160,20 +144,11 @@ export function HomePage() {
{!isLoading && !error && decks.length === 0 && (
<div className="text-center py-16 animate-fade-in">
<div className="w-16 h-16 mx-auto mb-4 bg-ivory rounded-2xl flex items-center justify-center">
- <svg
+ <FontAwesomeIcon
+ icon={faBoxOpen}
className="w-8 h-8 text-muted"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={1.5}
- d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
- />
- </svg>
+ />
</div>
<h3 className="font-display text-lg font-medium text-slate mb-2">
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPlus}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M12 4v16m8-8H4"
- />
- </svg>
+ />
Create Your First Deck
</button>
</div>
@@ -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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faPen}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
- />
- </svg>
+ />
</button>
<button
type="button"
@@ -258,20 +215,11 @@ export function HomePage() {
className="p-2 text-muted hover:text-error hover:bg-error/5 rounded-lg transition-colors"
title="Delete deck"
>
- <svg
+ <FontAwesomeIcon
+ icon={faTrash}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
- />
- </svg>
+ />
</button>
</div>
</div>
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 ? (
<span className="flex items-center justify-center gap-2">
- <svg
- className="animate-spin h-4 w-4"
- viewBox="0 0 24 24"
+ <FontAwesomeIcon
+ icon={faSpinner}
+ className="h-4 w-4 animate-spin"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- fill="none"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
Signing in...
</span>
) : (
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() {
<div className="min-h-screen bg-cream flex items-center justify-center px-4">
<div className="text-center animate-fade-in">
<div className="w-20 h-20 mx-auto mb-6 bg-ivory rounded-2xl flex items-center justify-center">
- <svg
+ <FontAwesomeIcon
+ icon={faFaceSadTear}
className="w-10 h-10 text-muted"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={1.5}
- d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
- />
- </svg>
+ />
</div>
<h1 className="font-display text-6xl font-bold text-ink mb-2">404</h1>
<h2 className="font-display text-xl font-medium text-slate mb-4">
@@ -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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faHouse}
className="w-5 h-5"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
- />
- </svg>
+ />
Go Home
</Link>
</div>
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"
>
- <svg
+ <FontAwesomeIcon
+ icon={faChevronLeft}
className="w-4 h-4"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M15 19l-7-7 7-7"
- />
- </svg>
+ />
Back to Deck
</Link>
</div>
@@ -269,26 +267,11 @@ export function StudyPage() {
{/* Loading State */}
{isLoading && (
<div className="flex-1 flex items-center justify-center">
- <svg
- className="animate-spin h-8 w-8 text-primary"
- viewBox="0 0 24 24"
+ <FontAwesomeIcon
+ icon={faSpinner}
+ className="h-8 w-8 text-primary animate-spin"
aria-hidden="true"
- >
- <circle
- className="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- strokeWidth="4"
- fill="none"
- />
- <path
- className="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
- />
- </svg>
+ />
</div>
)}
@@ -335,20 +318,11 @@ export function StudyPage() {
>
<div className="text-center py-12 px-6 bg-white rounded-2xl border border-border/50 shadow-card max-w-sm w-full">
<div className="w-16 h-16 mx-auto mb-4 bg-success/10 rounded-2xl flex items-center justify-center">
- <svg
+ <FontAwesomeIcon
+ icon={faCheck}
className="w-8 h-8 text-success"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M5 13l4 4L19 7"
- />
- </svg>
+ />
</div>
<h2 className="font-display text-xl font-medium text-slate mb-2">
All caught up!
@@ -374,20 +348,11 @@ export function StudyPage() {
>
<div className="text-center py-12 px-6 bg-white rounded-2xl border border-border/50 shadow-lg max-w-sm w-full animate-scale-in">
<div className="w-20 h-20 mx-auto mb-6 bg-success/10 rounded-full flex items-center justify-center">
- <svg
+ <FontAwesomeIcon
+ icon={faCircleCheck}
className="w-10 h-10 text-success"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
aria-hidden="true"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
- />
- </svg>
+ />
</div>
<h2 className="font-display text-2xl font-semibold text-ink mb-2">
Session Complete!