diff --git a/t29-project-1/src/App.tsx b/t29-project-1/src/App.tsx index 54f1f06..d9b3191 100644 --- a/t29-project-1/src/App.tsx +++ b/t29-project-1/src/App.tsx @@ -1,31 +1,30 @@ -import { useState } from 'react'; +import { useCatNavigation } from './hooks/useCatNavigation'; import { NavPrevious, NavNext } from './components/ActionButtons'; import { CatCard } from './components/CatCard'; -import { Favorites } from './components/Favorites'; import './App.css'; export default function App() { - const [isViewingFavorites, setIsViewingFavorites] = useState(false); - - const handlePrevious = () => {}; - const handleNext = () => {}; + const { + currentCat, + isLoading, + error, + canGoPrevious, + handleNext, + handlePrevious, + } = useCatNavigation(); return (
- {/* Top bar containing ONLY the Star Button */} -
- setIsViewingFavorites(!isViewingFavorites)} - isViewingFavorites={isViewingFavorites} +
- {/* 3-Column Navigation Grid */} -
); -} \ No newline at end of file +} diff --git a/t29-project-1/src/components/CatCard.css b/t29-project-1/src/components/CatCard.css index 33bd049..06bdd1c 100644 --- a/t29-project-1/src/components/CatCard.css +++ b/t29-project-1/src/components/CatCard.css @@ -1,31 +1,58 @@ -/* Card container using semantic article tag */ -article.cat-card { - width: 100%; - max-width: 420px; - min-height: 480px; - background-color: #ffffff; - border: 2px solid #e2e8f0; - border-radius: 16px; - padding: 1.5rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +/* Card Container */ +.cat-card-frame { + box-sizing: border-box; display: flex; flex-direction: column; align-items: center; - justify-content: center; - text-align: center; - margin: 0 auto; + width: 100%; + max-width: 400px; + background-color: #fff4c8; + border: 3px solid #7d5d75; + border-radius: 16px; + padding: 1rem; + gap: 0.75rem; } -/* Temporary image box placeholder */ -.card-image-placeholder { +/* Header Flex Layout to place Favorite in top-right */ +.card-header { width: 100%; - height: 300px; - background-color: #f1f5f9; - border: 2px dashed #cbd5e1; - border-radius: 12px; + align-items: center; + display: flex; + justify-content: flex-end; +} + +.favorite-action { + display: flex; + align-items: center; + justify-content: flex; + margin-left: auto; +} + +/* Image Viewport */ +.image-viewport { + box-sizing: border-box; + width: 90%; + aspect-ratio: 2/3; display: flex; align-items: center; justify-content: center; - color: #64748b; - font-weight: 500; + background-color: #fff4c8; + overflow: hidden; + margin: 0; +} + +.cat-image { + width: 100%; + height: 90%; + object-fit: contain; +} + +.status-text { + font-size: 0.95rem; + color: #7d5d75; + text-align: center; +} + +.status-text.error { + color: #dc2626; } diff --git a/t29-project-1/src/components/CatCard.tsx b/t29-project-1/src/components/CatCard.tsx index 5a84022..2f3f5ff 100644 --- a/t29-project-1/src/components/CatCard.tsx +++ b/t29-project-1/src/components/CatCard.tsx @@ -1,16 +1,50 @@ -import React from 'react'; +import { Favorites } from './Favorites'; +import { type CatData } from '../api/catApi'; import './CatCard.css'; -export const CatCard: React.FC = () => { +interface CatCardProps { + cat?: CatData | null; + isFavorite?: boolean; + onToggleFavorite?: () => void; + isLoading?: boolean; + error?: Error | null; +} + +export const CatCard = ({ + cat, + isFavorite = false, + onToggleFavorite, + isLoading = false, + error = null, +}: CatCardProps) => { return ( -
-
-

🐱 Cat Image Placeholder

+
+
+ +
+ +
+ {isLoading &&

Loading cat...

} + + {error &&

{error.message}

} + + {cat?.url && !isLoading && ( + Random cat + )} + + {!cat && !isLoading && !error && ( +

Use arrows to load a cat

+ )}
-

Breed Name Placeholder

-

Temperament description placeholder

); }; - -export default CatCard; diff --git a/t29-project-1/src/components/Favorites.tsx b/t29-project-1/src/components/Favorites.tsx index 9fc2e58..c97bcb6 100644 --- a/t29-project-1/src/components/Favorites.tsx +++ b/t29-project-1/src/components/Favorites.tsx @@ -20,6 +20,8 @@ export const Favorites: React.FC = ({ } aria-pressed={isViewingFavorites} > + {/*Generated with Gemini (September 2026) */} + {/*Prompt: Generate code that will create a star with rounded corners */} (() => { + try { + const savedHistory = localStorage.getItem(STORAGE_KEY_HISTORY); + return savedHistory ? JSON.parse(savedHistory) : []; + } catch { + return []; + } + }); + + const [currentIndex, setCurrentIndex] = useState(() => { + try { + const savedIndex = localStorage.getItem(STORAGE_KEY_INDEX); + return savedIndex !== null ? Number(savedIndex) : -1; + } catch { + return -1; + } + }); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const currentCat = history[currentIndex] || null; + const canGoPrevious = currentIndex > 0; + + // Sync state changes to localStorage + useEffect(() => { + try { + localStorage.setItem(STORAGE_KEY_HISTORY, JSON.stringify(history)); + localStorage.setItem(STORAGE_KEY_INDEX, currentIndex.toString()); + } catch (err) { + console.error('Failed to save cat history to localStorage:', err); + } + }, [history, currentIndex]); + + const handleNext = useCallback(async () => { + // If browsing backward in history, advance forward index + if (currentIndex < history.length - 1) { + setCurrentIndex((prev) => prev + 1); + return; + } + + // Fetch new cat + setIsLoading(true); + setError(null); + try { + const newCat = await fetchCat(); + setHistory((prev) => [...prev, newCat]); + setCurrentIndex((prev) => prev + 1); + } catch (err) { + setError(err instanceof Error ? err : new Error('Failed to load cat')); + } finally { + setIsLoading(false); + } + }, [currentIndex, history.length]); + + const handlePrevious = useCallback(() => { + if (canGoPrevious) { + setCurrentIndex((prev) => prev - 1); + } + }, [canGoPrevious]); + + return { + currentCat, + isLoading, + error, + canGoPrevious, + handleNext, + handlePrevious, + }; +}