Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion t29-project-1/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion t29-project-1/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"dotenv": "^17.4.2",
"express": "^5.2.1",
"react": "^19.2.8",
"react-dom": "^19.2.8"
"react-dom": "^19.2.8",
"react-router-dom": "^7.18.4"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
6 changes: 6 additions & 0 deletions t29-project-1/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
flex-shrink: 0;
}

.favorite-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}

/* Mobile Responsive Adjustments */
@media (max-width: 600px) {
.nav-layout {
Expand Down
13 changes: 11 additions & 2 deletions t29-project-1/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useCatNavigation } from './hooks/useCatNavigation';
import { useFavoriteCat } from './hooks/useFavoriteCat';
import { NavPrevious, NavNext } from './components/ActionButtons';
import { CatCard } from './components/CatCard';
import { Link } from 'react-router-dom';
import './App.css';

export default function App() {
Expand All @@ -12,16 +14,23 @@ export default function App() {
handleNext,
handlePrevious,
} = useCatNavigation();

const { isFavorite, toggleFavorite } = useFavoriteCat(currentCat);
return (
<main className="app-container">
<Link to="/favorite">Favorite</Link>
<nav className="nav-layout" aria-label="Main content navigation">
<NavPrevious
onClick={handlePrevious}
disabled={!canGoPrevious || isLoading}
/>

<CatCard cat={currentCat} isLoading={isLoading} error={error} />
<CatCard
cat={currentCat}
isFavorite={isFavorite}
isLoading={isLoading}
error={error}
onToggleFavorite={toggleFavorite}
/>

<NavNext onClick={handleNext} disabled={isLoading} />
</nav>
Expand Down
5 changes: 5 additions & 0 deletions t29-project-1/src/components/Favorites.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
stroke 0.15s ease;
}

.star-svg.is-active {
fill: #7d5d75;
stroke: #fff4c8;
}

/* Invert colors on BOTH hover and keyboard focus */
.star-btn:hover .star-svg,
.star-btn:focus-visible .star-svg {
Expand Down
4 changes: 2 additions & 2 deletions t29-project-1/src/components/Favorites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import './Favorites.css';

interface FavoritesProps {
onToggleFavorites: () => void;
onToggleFavorites?: () => void;
isViewingFavorites?: boolean;
}

Expand All @@ -16,7 +16,7 @@ export const Favorites: React.FC<FavoritesProps> = ({
className="star-btn"
onClick={onToggleFavorites}
aria-label={
isViewingFavorites ? 'Return to cat browsing' : 'Go to favorites'
isViewingFavorites ? 'Remove from favorites' : 'Add to favorites'
}
aria-pressed={isViewingFavorites}
>
Expand Down
29 changes: 29 additions & 0 deletions t29-project-1/src/hooks/useFavoriteCat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from 'react';
import type { CatData } from '../api/catApi';
import {
addFavoriteCat,
getFavoriteCats,
removeFavoriteCat,
} from '../utils/favorites';

export function useFavoriteCat(currentCat: CatData | null) {
const [favoriteCats, setFavoriteCats] = useState(getFavoriteCats);
const isFavorite = Boolean(
currentCat?.id &&
favoriteCats.some((favorite) => favorite.id === currentCat.id)
);

const toggleFavorite = () => {
if (!currentCat?.id) return;

if (isFavorite) {
removeFavoriteCat(currentCat.id);
} else {
addFavoriteCat(currentCat);
}

setFavoriteCats(getFavoriteCats());
};

return { isFavorite, toggleFavorite };
}
9 changes: 8 additions & 1 deletion t29-project-1/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './index.css';
import App from './App.tsx';
import FavoritePage from './pages/FavoritePage.tsx';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -16,7 +18,12 @@ const queryClient = new QueryClient({
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/favorite" element={<FavoritePage />} />
</Routes>
</BrowserRouter>
</QueryClientProvider>
</StrictMode>
);
37 changes: 37 additions & 0 deletions t29-project-1/src/pages/FavoritePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { CatCard } from '../components/CatCard';
import { getFavoriteCats, removeFavoriteCat } from '../utils/favorites';
import '../App.css';

export default function FavoritePage() {
const [favoriteCats, setFavoriteCats] = useState(getFavoriteCats);

const handleRemove = (catId?: string) => {
if (!catId) return;

removeFavoriteCat(catId);
setFavoriteCats(getFavoriteCats());
};

return (
<main>
<Link to="/">Back to cats</Link>
<h1>Favorites</h1>
{favoriteCats.length === 0 ? (
<p>No favorite cats yet.</p>
) : (
<section className="favorite-grid" aria-label="Favorite cats">
{favoriteCats.map((cat) => (
<CatCard
key={cat.id ?? cat.url}
cat={cat}
isFavorite
onToggleFavorite={() => handleRemove(cat.id)}
/>
))}
</section>
)}
</main>
);
}