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
35 changes: 17 additions & 18 deletions t29-project-1/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<main className="app-container">
{/* Top bar containing ONLY the Star Button */}
<header className="top-bar">
<Favorites
onToggleFavorites={() => setIsViewingFavorites(!isViewingFavorites)}
isViewingFavorites={isViewingFavorites}
<nav className="nav-layout" aria-label="Main content navigation">
<NavPrevious
onClick={handlePrevious}
disabled={!canGoPrevious || isLoading}
/>
</header>

{/* 3-Column Navigation Grid */}
<nav className="nav-layout" aria-label="Main content navigation">
<NavPrevious onClick={handlePrevious} />
<CatCard />
<NavNext onClick={handleNext} />
<CatCard cat={currentCat} isLoading={isLoading} error={error} />

<NavNext onClick={handleNext} disabled={isLoading} />
</nav>
</main>
);
}
}
69 changes: 48 additions & 21 deletions t29-project-1/src/components/CatCard.css
Original file line number Diff line number Diff line change
@@ -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;
}
52 changes: 43 additions & 9 deletions t29-project-1/src/components/CatCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<article className="cat-card" aria-label="Cat profile card">
<figure className="card-image-placeholder">
<p>🐱 Cat Image Placeholder</p>
<article className="cat-card-frame">
<header className="card-header">
<Favorites
isViewingFavorites={isFavorite}
onToggleFavorites={onToggleFavorite}
/>
</header>

<figure className="image-viewport">
{isLoading && <p className="status-text">Loading cat...</p>}

{error && <p className="status-text error">{error.message}</p>}

{cat?.url && !isLoading && (
<img
src={cat.url}
alt="Random cat"
width={cat.width}
height={cat.height}
className="cat-image"
/>
)}

{!cat && !isLoading && !error && (
<p className="status-text">Use arrows to load a cat</p>
)}
</figure>
<h3>Breed Name Placeholder</h3>
<p>Temperament description placeholder</p>
</article>
);
};

export default CatCard;
2 changes: 2 additions & 0 deletions t29-project-1/src/components/Favorites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const Favorites: React.FC<FavoritesProps> = ({
}
aria-pressed={isViewingFavorites}
>
{/*Generated with Gemini (September 2026) */}
{/*Prompt: Generate code that will create a star with rounded corners */}
<svg
className={`star-svg ${isViewingFavorites ? 'is-active' : ''}`}
viewBox="-2 -2 28 28"
Expand Down
77 changes: 77 additions & 0 deletions t29-project-1/src/hooks/useCatNavigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState, useCallback, useEffect } from 'react';
import { fetchCat, type CatData } from '../api/catApi';

const STORAGE_KEY_HISTORY = 'cat_app_history';
const STORAGE_KEY_INDEX = 'cat_app_index';

export function useCatNavigation() {
const [history, setHistory] = useState<CatData[]>(() => {
try {
const savedHistory = localStorage.getItem(STORAGE_KEY_HISTORY);
return savedHistory ? JSON.parse(savedHistory) : [];
} catch {
return [];
}
});

const [currentIndex, setCurrentIndex] = useState<number>(() => {
try {
const savedIndex = localStorage.getItem(STORAGE_KEY_INDEX);
return savedIndex !== null ? Number(savedIndex) : -1;
} catch {
return -1;
}
});

const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(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,
};
}