Skip to content

Commit

Permalink
favoriting system implemented locally
Browse files Browse the repository at this point in the history
  • Loading branch information
zvoverman committed May 18, 2024
1 parent 5f23f64 commit 6ad77ed
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 93 deletions.
4 changes: 4 additions & 0 deletions src/components/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
flex-wrap: wrap;
}

.toggle-buttons {
padding: 1rem;
}

@keyframes App-logo-spin {
from {
transform: rotate(0deg);
Expand Down
116 changes: 82 additions & 34 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,97 @@
import React, { useState } from 'react';
import logo from '../logo.svg';
import { QueryClient, QueryClientProvider } from 'react-query';
import './App.css';
import PersonCard from './PersonCard';
import ToggleButton from 'react-bootstrap/ToggleButton';
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';

const queryClient = new QueryClient();

function App() {
const [length, setLength] = useState(12); // Initial length is set to 12
const [length, setLength] = useState<number>(12);
const [favoriteCards, setFavoriteCards] = useState<number[]>([]);
const [showFavorites, setShowFavorites] = useState<boolean>(false);

const handleInputChange = (event: any) => {
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(event.target.value);
let result = Math.min(value, 50); // while <input max="50">, greater numbers can be entered into the input -> ensure maximum of 50 to limit API calls
console.log(result);
setLength(result);
if (!isNaN(value)) {
// cap maximum number of cards to limit API calls and ignore invalid data
let result = Math.min(Math.max(value, 1), 50);
setLength(result);
}
};

// Call AWS lambda to update DynamoDB here!!
const handleFavoriteToggle = (cardIndex: number) => {
setFavoriteCards(prevFavorites =>
prevFavorites.includes(cardIndex)
? prevFavorites.filter(id => id !== cardIndex)
: [...prevFavorites, cardIndex]
);
};

// Switch between 'All-Items' and 'Favorites'
const handleToggleChange = (value: number) => {
setShowFavorites(value === 2);
};

const displayedCards = showFavorites
? favoriteCards
: Array.from({ length }, (_, index) => index);

return (
<div className="App">
<header className="App-header">
<h2 className="App-title">SWAPI React + Typescript Test!</h2>
<a
className="SWAPI-link"
href="https://swapi.dev/"
target="_blank"
rel="noopener noreferrer"
<QueryClientProvider client={queryClient}>
<div className="App">
<header className="App-header">
<h2 className="App-title">SWAPI React + Typescript Test!</h2>
<a
className="SWAPI-link"
href="https://swapi.dev/"
target="_blank"
rel="noopener noreferrer"
>
Star Wars API
</a>
</header>
<div className="App-input">
<label htmlFor="lengthInput"># of People Cards Displayed:</label>
<input
type="number"
id="lengthInput"
value={length}
onChange={handleInputChange}
min="1"
max="50"
/>
</div>

<ToggleButtonGroup
className="toggle-buttons"
type="radio"
name="options"
defaultValue={1}
onChange={handleToggleChange}
>
Star Wars API
</a>
</header>
<div className="App-input">
<label htmlFor="lengthInput"># of People Cards Displayed:</label>
<input
type="number"
id="lengthInput"
value={length}
onChange={handleInputChange}
min="1"
max="50"
/>
</div>
{/* Create an array of specified length and render a PersonCard component for each element*/}
<div className="App-cards">
{Array.from({ length }).map((_, index) => (
<PersonCard key={index} index={index} />
))}
<ToggleButton id="tbg-radio-1" value={1}>
All-Items
</ToggleButton>
<ToggleButton id="tbg-radio-2" value={2}>
Favorites
</ToggleButton>
</ToggleButtonGroup>

<div className="App-cards">
{displayedCards.map(index => (
<PersonCard
key={index}
index={index}
isFavorite={favoriteCards.includes(index)}
onFavoriteToggle={handleFavoriteToggle}
/>
))}
</div>
</div>
</div>
</QueryClientProvider>
);
}

Expand Down
80 changes: 46 additions & 34 deletions src/components/PersonCard.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
import { useQuery } from 'react-query';
import Card from 'react-bootstrap/Card';

import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Link } from 'react-router-dom'

const queryClient = new QueryClient()
import ToggleButton from 'react-bootstrap/ToggleButton';

interface PersonCardProps {
index: number;
};

interface CharacterDataProps {
person: number;
isFavorite: boolean;
onFavoriteToggle: (cardIndex: number) => void;
}

function PersonCard({ index }: PersonCardProps) {
return (
<QueryClientProvider client={queryClient}>
<CharacterData person={index + 1} />
</QueryClientProvider>
);
interface CharacterData {
name: string;
height: string;
mass: string;
eye_color: string;
hair_color: string;
skin_color: string;
}

function CharacterData({ person }: CharacterDataProps): JSX.Element {
function PersonCard({ index, isFavorite, onFavoriteToggle }: PersonCardProps): JSX.Element {

// e.g. https://swapi.dev/api/people/1
const { isLoading, error, data } = useQuery('getPerson_' + person, () =>
fetch('https://swapi.dev/api/people/' + person).then(res =>
// api starts at 1 not 0
const person = index+1;

const { isLoading, error, data } = useQuery<CharacterData>('getPerson_' + person, () =>
fetch('https://swapi.dev/api/people/' + person).then(res =>
res.json()
)
);

if (isLoading) return (<p>Loading...</p>);

if (error) return (<p>Error.</p>);
if (error) return (<p>Error fetching data.</p>);

// If request returns an empty response...
if (!data.name) {
if (!data?.name) {
return (
<Card style={{ width: '18rem' }}>
<Card.Body>
Expand All @@ -45,19 +42,34 @@ function CharacterData({ person }: CharacterDataProps): JSX.Element {
);
}

// Populate card with API data
const handleFavoriteClick = () => {
onFavoriteToggle(index);
};

return (
<Card style={{ width: '18rem' }} >
<Card.Body>
<Card.Title>{data.name}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">{data.height + ' cm, ' + data.mass + ' kg'}</Card.Subtitle>
<Card.Text>
{data.name} has {data.eye_color} eyes, {data.hair_color} hair, and a {data.skin_color} skintone.
</Card.Text>
<Card.Link href={'https://en.wikipedia.org/wiki/' + data.name.replace(/ /g, "_")}>Wikipedia</Card.Link>
</Card.Body>
</Card>
<Card style={{ width: '18rem' }}>
<Card.Body>
<ToggleButton
id={"favorite-btn-" + person}
variant={isFavorite ? 'primary' : 'outline-primary'}
value={1}
onClick={handleFavoriteClick}
>
{isFavorite ? 'Unfavorite' : 'Favorite'}
</ToggleButton>
<Card.Title>{data.name}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">
{data.height} cm, {data.mass} kg
</Card.Subtitle>
<Card.Text>
{data.name} has {data.eye_color} eyes, {data.hair_color} hair, and a {data.skin_color} skintone.
</Card.Text>
<Card.Link href={'https://en.wikipedia.org/wiki/' + data.name.replace(/ /g, "_")}>
Wikipedia
</Card.Link>
</Card.Body>
</Card>
);
}

export default PersonCard;
export default PersonCard;
25 changes: 0 additions & 25 deletions src/components/PersonView.tsx

This file was deleted.

0 comments on commit 6ad77ed

Please sign in to comment.