Skip to content

Commit

Permalink
feat: (frontend) update for species and variety models
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Potter committed Nov 1, 2024
1 parent 206b19b commit 3f28828
Show file tree
Hide file tree
Showing 19 changed files with 2,539 additions and 1,299 deletions.
12 changes: 6 additions & 6 deletions frontend/src/components/pages/PokemonPage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type {Meta, StoryObj} from '@storybook/react';

import {PokemonPage} from './PokemonPage.tsx';
import {delay, http, HttpResponse} from "msw";
import pokemon1_10 from "../../fixtures/pokemon1-10.json";
import pokemon11_20 from "../../fixtures/pokemon-11-20.json";
import pokemon_page_1 from "../../fixtures/pokemon_page_1.json";
import pokemon_page_2 from "../../fixtures/pokemon_page_2.json";
import {reactRouterParameters, withRouter} from "storybook-addon-remix-react-router";


Expand All @@ -30,10 +30,10 @@ export const Default: Story = {
msw: {
handlers: [
http.get('/api/v1/pokemon?page=1&pageSize=12', async () => {
return HttpResponse.json(pokemon1_10);
return HttpResponse.json(pokemon_page_1);
}),
http.get('/api/v1/pokemon?page=2&pageSize=12', async () => {
return HttpResponse.json(pokemon11_20);
return HttpResponse.json(pokemon_page_2);
}),
],
},
Expand All @@ -45,8 +45,8 @@ export const WithLoading: Story = {
msw: {
handlers: [
http.get('/api/v1/pokemon', async () => {
await delay(800)
return HttpResponse.json(pokemon1_10);
await delay(2800)
return HttpResponse.json(pokemon_page_1);
}),
],
},
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/pages/PokemonPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import {EvolutionChain} from "../pokemon/EvolutionChain.tsx";
import {UnknownTab} from "../ui/UnknownTab.tsx";
import {ArrowLeftIcon, ArrowRightIcon, ArrowUturnLeftIcon, FunnelIcon} from "@heroicons/react/24/outline";
import useAxios from "axios-hooks";
import {Pokemon} from "../../models/pokemon.ts";
import {ListPokemonParams, ListPokemonResponse, PokemonSpecies} from "../../models/pokemon.ts";
import {useNavigate, useParams} from "react-router-dom";
import {useDebounce} from "use-debounce";
import {ListPokemonParams, PokemonResponse} from "../../models/interfaces.ts";
import {FilterForm} from "../pokemon/FilterForm.tsx";


Expand All @@ -33,7 +32,7 @@ export function PokemonPage() {

const [tabIndex, setTabIndex] = useState(0);

const [{data, loading}, refetch] = useAxios<PokemonResponse>(
const [{data, loading}, refetch] = useAxios<ListPokemonResponse>(
{
url: '/api/v1/pokemon',
params: {
Expand All @@ -44,7 +43,7 @@ export function PokemonPage() {
}
)

const [currentPokemon, setCurrentPokemon] = useState<Pokemon | null>(null);
const [currentPokemon, setCurrentPokemon] = useState<PokemonSpecies | null>(null);

const handlePrev = useCallback(() => {

Expand Down Expand Up @@ -145,7 +144,7 @@ export function PokemonPage() {

return <Pokedex>
<Pokedex.LeftScreen loading={loading}>
{tabIndex === 0 ? <PokemonSummary pokemon={currentPokemon}/> : tabIndex === 1 ?
{tabIndex === 0 ? <PokemonSummary pokemonSpecies={currentPokemon}/> : tabIndex === 1 ?
<PokemonDetails pokemon={currentPokemon}/> : tabIndex === 2 ?
<EvolutionChain pokemon={currentPokemon}/> : <UnknownTab/>}
</Pokedex.LeftScreen>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pokemon/EvolutionChain.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Pokemon} from "../../models/pokemon.ts";
import {PokemonSpecies} from "../../models/pokemon.ts";
import {ComingSoon} from "../ui/ComingSoon.tsx";

export const EvolutionChain = ({}: { pokemon: Pokemon | null }) => <ComingSoon/>;
export const EvolutionChain = ({}: { pokemon: PokemonSpecies | null }) => <ComingSoon/>;
2 changes: 1 addition & 1 deletion frontend/src/components/pokemon/FilterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ListPokemonParams} from "../../models/interfaces.ts";
import {FormEventHandler, useContext} from "react";
import {MoveTypeContext} from "../../context/MoveTypeContext.tsx";
import {capitalize} from "lodash";
import {ListPokemonParams} from "../../models/pokemon.ts";

export const FilterForm = ({onSubmit}: { onSubmit: (params: ListPokemonParams) => void }) => {

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pokemon/PokemonDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Pokemon} from "../../models/pokemon.ts";
import {PokemonSpecies} from "../../models/pokemon.ts";
import {ComingSoon} from "../ui/ComingSoon.tsx";

export const PokemonDetails = ({}: { pokemon: Pokemon | null }) => (
export const PokemonDetails = ({}: { pokemon: PokemonSpecies | null }) => (
<ComingSoon/>);
5 changes: 2 additions & 3 deletions frontend/src/components/pokemon/PokemonList.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type {Meta, StoryObj} from '@storybook/react';

import {PokemonList} from './PokemonList.tsx';
import mockPokemon from '../../fixtures/pokemon.json'
import mockPokemon from '../../fixtures/pokemon_page_1.json'
import Pokedex from "../ui/pokedex/Pokedex.tsx";
import {Pokemon} from "../../models/pokemon.ts";

const meta = {
component: PokemonList,
Expand All @@ -21,7 +20,7 @@ type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
pokemon: mockPokemon.pokemon as unknown as Pokemon[],
pokemon: mockPokemon.pokemon,
pokemonId: '4',
onPokemonSelect: () => {
}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/pokemon/PokemonList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import {Pokemon} from "../../models/pokemon.ts";
import {PokemonSpecies} from "../../models/pokemon.ts";
import {PokemonListItem} from "./PokemonListItem.tsx";

interface PokemonListProps {
pokemon: Pokemon[];
pokemon: PokemonSpecies[];
pokemonId: string | undefined;
onPokemonSelect: (pokemonId: number) => void;
}
Expand All @@ -15,7 +15,7 @@ export const PokemonList: React.FC<PokemonListProps> = ({pokemon, pokemonId, onP
{pokemon.map((pokemon, index) => {
return <PokemonListItem onPokemonSelect={onPokemonSelect} key={`${pokemon.name}_list_item`}
active={pokemonId ? pokemon.id === parseInt(pokemonId) : index === 0}
pokemon={pokemon}/>
pokemonSpecies={pokemon}/>
})}
</div>)
}
5 changes: 2 additions & 3 deletions frontend/src/components/pokemon/PokemonListItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type {Meta, StoryObj} from '@storybook/react';
import pokemon from '../../fixtures/pokemon.json'
import pokemon_page_1 from '../../fixtures/pokemon_page_1.json'

import {PokemonListItem} from './PokemonListItem.tsx';
import {Pokemon} from "../../models/pokemon.ts";

const meta = {
component: PokemonListItem,
Expand All @@ -19,7 +18,7 @@ type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
pokemon: pokemon.pokemon[0] as unknown as Pokemon,
pokemonSpecies: pokemon_page_1.pokemon[0],
active: true,
onPokemonSelect: () => {
}
Expand Down
26 changes: 12 additions & 14 deletions frontend/src/components/pokemon/PokemonListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import {Pokemon} from "../../models/pokemon.ts";
import {PokemonSpecies} from "../../models/pokemon.ts";
import {capitalize} from "lodash";
import {MouseEventHandler} from "react";

interface PokemonListItemProps {
pokemon: Pokemon,
pokemonSpecies: PokemonSpecies,
active?: boolean,
onPokemonSelect: (pokemonId: number) => void
}

export const PokemonListItem = ({
pokemon,
pokemonSpecies,
onPokemonSelect,
active,

}: PokemonListItemProps) => {


const pokemon = pokemonSpecies.varieties[0];


const handleSelect: MouseEventHandler<HTMLButtonElement> = (e) => {
e.preventDefault();
onPokemonSelect(pokemon.id)
pokemon ? onPokemonSelect(pokemon.id) : console.error("No default variety found")

}

return (<button
onClick={handleSelect}
className={`flex items-center justify-between lg:space-x-3 bg-sky-500 sm:px-2 sm:py-1 md:px-4 md:py-2.5 rounded-lg shadow-lg ${!active ? "opacity-70" : ''}`}>
<div className={'flex flex-col items-start justify-center space-y-1'}>
<h2 className={'hidden md:block text-xs lg:text-md font-bold'}>{capitalize(pokemon.name)}</h2>
<img className={'hidden md:block w-16 lg:w-20 h-auto'} src={pokemon.primary_type.img_url}
alt={pokemon.primary_type.name}/>
{pokemon.secondary_type &&
<img className={'hidden md:block w-16 lg:w-20 h-auto'} src={pokemon.secondary_type.img_url}
alt={pokemon.secondary_type.name}/>}
</div>
<img src={pokemon.sprite_url} alt={pokemon.name} className={'h-auto w-12 sm:w-16 mx-auto'}/>
className={`flex items-center justify-between lg:space-x-3 bg-sky-500 sm:px-2 sm:py-1 md:px-3 rounded-lg shadow-lg ${!active ? "opacity-70" : ''}`}>
<h2 className={'hidden md:block text-xs lg:text-md font-bold'}>{capitalize(pokemon?.name)}</h2>
<img src={pokemon?.sprite_url} alt={pokemon?.name} className={'h-auto w-12 sm:w-16 md:w-[4.75rem] mx-auto'}/>
</button>)
}
74 changes: 58 additions & 16 deletions frontend/src/components/pokemon/PokemonSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import {Pokemon} from "../../models/pokemon.ts";
import {PokemonSpecies, PokemonVariety} from "../../models/pokemon.ts";
import {SummaryItem} from "../ui/SummaryItem.tsx";
import {capitalize} from "lodash";
import {useEffect, useRef, useState} from "react";
import {useCallback, useEffect, useRef, useState} from "react";
import {PlayIcon} from "@heroicons/react/24/solid";
import {isSafari} from 'react-device-detect';
import {ChevronLeftIcon, ChevronRightIcon} from "@heroicons/react/24/outline";

interface PokemonSummaryProps {
pokemonSpecies: PokemonSpecies | null
}

export const PokemonSummary = ({pokemonSpecies}: PokemonSummaryProps) => {

const [selectedPokemonVariety, setSelectedPokemonVariety] = useState<PokemonVariety>();
const [varietyIndex, setVarietyIndex] = useState(0);

export const PokemonSummary = ({pokemon}: { pokemon: Pokemon | null }) => {
const audioRef = useRef<HTMLAudioElement>(null);
const [progress, setProgress] = useState(0);

Expand All @@ -14,9 +23,13 @@ export const PokemonSummary = ({pokemon}: { pokemon: Pokemon | null }) => {
audioRef.current && audioRef?.current.play();
};

useEffect(() => {
setVarietyIndex(0)
}, [pokemonSpecies]);


useEffect(() => {
if (!pokemon) {
if (!selectedPokemonVariety) {
return
}

Expand All @@ -38,7 +51,14 @@ export const PokemonSummary = ({pokemon}: { pokemon: Pokemon | null }) => {
clearTimeout(to)
}

}, [pokemon?.cry]);
}, [selectedPokemonVariety?.cry]);


useEffect(() => {
if (!pokemonSpecies) return;
setSelectedPokemonVariety(pokemonSpecies?.varieties[varietyIndex]);
}, [pokemonSpecies, varietyIndex]);


useEffect(() => {
let timeout: NodeJS.Timeout;
Expand All @@ -52,12 +72,17 @@ export const PokemonSummary = ({pokemon}: { pokemon: Pokemon | null }) => {

}, [progress]);


const showVariety = useCallback(() => {
return pokemonSpecies && pokemonSpecies?.varieties.length > 1 && selectedPokemonVariety && selectedPokemonVariety?.name?.split("-").length > 2;
}, [pokemonSpecies, selectedPokemonVariety]);

return (
<section className={'flex flex-col items-center justify-center font-mono h-full'}>
<section className={'relative flex flex-col items-center justify-center font-mono h-full'}>
<div className={'max-w-md flex flex-col flex-grow w-full justify-between'}>
<h1 className="sm:text-lg md:text-3xl lg:text-4xl">{capitalize(pokemon?.name)} #{pokemon?.id}</h1>
<h1 className="sm:text-lg md:text-3xl lg:text-4xl z-10">{pokemonSpecies && pokemonSpecies.name?.split("-").length > 1 ? `${capitalize(pokemonSpecies?.varieties[0]?.name.split("-")[0])} ${capitalize(pokemonSpecies?.varieties[0]?.name.split("-")[1])}` : capitalize(pokemonSpecies?.varieties[0]?.name)} #{pokemonSpecies?.varieties[0]?.id} {varietyIndex > 0 ? showVariety() ? capitalize(`${selectedPokemonVariety?.name.split("-")[1]}-${capitalize(selectedPokemonVariety?.name.split("-")[2])}`) : capitalize(selectedPokemonVariety?.name.split("-")[1]) : <></>}</h1>
<div className="translate-y-[10%] flex items-center justify-end sm:justify-center">
<img src={pokemon?.sprite_url} alt={pokemon?.name}
<img src={selectedPokemonVariety?.sprite_url} alt={selectedPokemonVariety?.name}
className={'h-auto w-2/5 md:w-3/4 animate-bounce'}/>
</div>
</div>
Expand All @@ -76,16 +101,33 @@ export const PokemonSummary = ({pokemon}: { pokemon: Pokemon | null }) => {
</div>
</div>
</div>
<audio ref={audioRef} src={pokemon?.cry}/>
<audio ref={audioRef} src={selectedPokemonVariety?.cry}/>
</div>}
<SummaryItem><img className={'w-16 xs:w-20 sm:w-auto mx-auto'} src={pokemon?.primary_type.img_url}
alt={pokemon?.primary_type.name}/></SummaryItem>
<SummaryItem>{pokemon?.secondary_type?.name ?
<img className={'w-16 xs:w-20 sm:w-auto mx-auto'} src={pokemon.secondary_type.img_url}
alt={pokemon.secondary_type.img_url}/> : 'None'}</SummaryItem>
<SummaryItem>Weight: {(pokemon?.weight ?? 0) / 10}kg</SummaryItem>
<SummaryItem>Height: {(pokemon?.height ?? 0) / 10}m</SummaryItem>
<SummaryItem><img className={'w-16 xs:w-20 sm:w-auto mx-auto'}
src={selectedPokemonVariety?.primary_type.img_url}
alt={selectedPokemonVariety?.primary_type.name}/></SummaryItem>
<SummaryItem>{selectedPokemonVariety?.secondary_type?.name ?
<img className={'w-16 xs:w-20 sm:w-auto mx-auto'}
src={selectedPokemonVariety.secondary_type.img_url}
alt={selectedPokemonVariety.secondary_type.img_url}/> : 'None'}</SummaryItem>
<SummaryItem>Weight: {(selectedPokemonVariety?.weight ?? 0) / 10}kg</SummaryItem>
<SummaryItem>Height: {(selectedPokemonVariety?.height ?? 0) / 10}m</SummaryItem>
</div>

{pokemonSpecies && pokemonSpecies?.varieties.length > 1 && <>
<button disabled={!pokemonSpecies?.varieties[varietyIndex - 1]}
className={`absolute top-1/4 -left-3 md:top-1/3`}
onClick={() => setVarietyIndex(varietyIndex - 1)}>
<ChevronLeftIcon
className={'w-5 h-5'}/></button>
<button disabled={!pokemonSpecies?.varieties[varietyIndex + 1]}
className={'absolute top-1/4 -right-3 md:top-1/3'}
onClick={() => setVarietyIndex(varietyIndex + 1)}>
<ChevronRightIcon
className={'w-5 h-5'}/></button>
</>}


</section>
)
};
2 changes: 1 addition & 1 deletion frontend/src/components/ui/pokedex/Pokedex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {KeyboardButton, KeyboardButtonContainer} from "./KeyboardButton.tsx";

export const PokedexRoot: React.FC<PropsWithChildren> = ({children}) => (
<div
className={"relative grid grid-cols-1 gap-0.5 sm:gap-2 md:grid-cols-2 md:grid-rows-7 md:gap-x-3 bg-red-700 px-3 sm:px-12 md:px-6 rounded-xl max-w-md md:max-w-4xl lg:max-w-5xl xl:max-w-6xl mx-0.5 xs:mx-1 md:mx-3 lg:mx-4 xl:mx-auto pt-12 sm:pt-8 pb-6 sm:min-h-[726px] max-h-screen shadow-2xl dark:shadow-gray-800"}>
className={"relative grid grid-cols-1 gap-0.5 sm:gap-2 md:grid-cols-2 md:grid-rows-7 md:gap-x-3 bg-red-700 px-3 sm:px-12 md:px-6 rounded-xl max-w-md md:max-w-4xl lg:max-w-5xl xl:max-w-6xl mx-1 xs:mx-auto pt-12 sm:pt-8 pb-6 sm:min-h-[726px] max-h-screen shadow-2xl dark:shadow-gray-800"}>
{children}
<div
className="h-0.5 sm:h-1 rounded-full bg-slate-700 w-full md:col-span-2 mx-auto self-end mt-1 sm:mt-1.5"></div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ui/pokedex/PokedexScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ const RightScreenLoading: React.FC = () => {
</div>
}
export const LeftScreenContainer: React.FC<PropsWithChildren<Loadable>> = ({children, loading}) => <div
className={`max-h-72 min-h-64 xs:max-h-96 xs:min-h-80 md:max-h-full md:min-h-[590px] border-slate-400 border-2 md:border-4 bg-sky-700 text-sky-50 rounded-lg rounded-b-none md:rounded-b-lg p-4 md:row-span-6`}>
className={`max-h-72 min-h-64 xs:max-h-96 xs:min-h-80 md:max-h-full md:min-h-[575px] border-slate-400 border-2 md:border-4 bg-sky-700 text-sky-50 rounded-lg rounded-b-none md:rounded-b-lg p-4 md:row-span-6`}>
{loading ? <LeftScreenLoading/> : children}
</div>;
export const RightScreenContainer: React.FC<PropsWithChildren<Sizeable & Loadable>> = ({
children,
loading = false,
size = 'small'
}) => <div
className={`min-h-40 max-h-44 sm:max-h-60 md:max-h-full md:min-h-[590px] border-slate-400 border-2 md:border-4 bg-sky-700 text-sky-50 rounded-lg rounded-t-none md:rounded-t-lg px-1 py-1 xs:px-3 xs:py-2 overflow-scroll ${size === 'small' ? "md:row-span-5" : 'md:row-span-6'}`}>
className={`min-h-40 max-h-44 sm:max-h-60 md:max-h-full md::min-h-[575px] border-slate-400 border-2 md:border-4 bg-sky-700 text-sky-50 rounded-lg rounded-t-none md:rounded-t-lg px-1 py-1 xs:px-3 xs:py-2 overflow-scroll ${size === 'small' ? "md:row-span-5" : 'md:row-span-6'}`}>
{loading ? <RightScreenLoading/> : children}
</div>;
2 changes: 1 addition & 1 deletion frontend/src/components/ui/pokedex/TabButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const TabButton: FC<TabButtonProps> = ({onClick, type, pulse}) => {
export const TabButtonContainer: FC<PropsWithChildren> = ({children}) => {

return <div
className="absolute top-1.5 left-3 sm:top-8 sm:left-1 sm:h-1/3 md:h-auto px-1 py-1 md:px-3 md:py-2 md:static flex flex-row sm:flex-col md:flex-row gap-6 lg:gap-12 xl:gap-16 bg-red-600 rounded-full items-center justify-center">
className="absolute top-1.5 left-3 sm:top-8 sm:left-1 sm:h-1/3 md:h-auto px-1 py-1 xl:px-3 xl:py-2 md:static flex flex-row sm:flex-col md:flex-row gap-6 lg:gap-12 xl:gap-16 bg-red-600 rounded-full items-center justify-center">
{children}
</div>;
};
Loading

0 comments on commit 3f28828

Please sign in to comment.