Compare commits

...

2 Commits

7 changed files with 140 additions and 85 deletions

View File

@ -8,20 +8,12 @@ import { useAppSelector } from 'app/hooks';
function App() {
const selectedRegion = useAppSelector(state => state.filter.selectedRegion);
const selectedType = useAppSelector(state => state.filter.selectedType);
const selectedSort = useAppSelector(state => state.filter.selectedSort);
const selectedSearchInput = useAppSelector(state => state.filter.searchInput);
return (
<div className="App app_container">
<Header />
<Filters />
<Pokedex
selectedRegion={selectedRegion}
selectedType={selectedType}
selectedSort={selectedSort}
searchInput={selectedSearchInput}
/>
<Pokedex selectedRegion={selectedRegion} />
<InfoDialog />
</div>
);

View File

@ -4,12 +4,12 @@ import {
PayloadAction,
Slice,
} from '@reduxjs/toolkit';
import { FilterState } from './types/slice';
import { FilterStateProps } from './types/slice';
import { RegionPokemonRange } from './types/slice';
import { pokeRestApi } from 'app/services/pokeRestApi';
import { fetchPokemonsInTheRegion } from 'features/Pokedex/pokedexSlice';
const initialState: FilterState = {
export const initialState: FilterStateProps = {
regionOptions: [],
typeOptions: [],
sortOptions: [],
@ -46,7 +46,7 @@ export const initializeFilterSlice = createAsyncThunk(
},
);
export const filterSlice: Slice<FilterState> = createSlice({
export const filterSlice: Slice<FilterStateProps> = createSlice({
name: 'filter',
initialState,
reducers: {

View File

@ -1,4 +1,4 @@
export type FilterState = {
export type FilterStateProps = {
regionOptions: RegionPokemonRange[];
typeOptions: string[];
sortOptions: { name: string; value: string }[];

View File

@ -3,8 +3,10 @@ import { configureStore, createSlice } from '@reduxjs/toolkit';
import type { Meta } from '@storybook/react';
import Pokedex from './Pokedex';
import { initialState } from './pokedexSlice';
import { initialState as initialPokedexState } from './pokedexSlice';
import { initialState as initialFilterState } from '../Filters/filterSlice';
import { PokedexStateProps } from './types/slice';
import { FilterStateProps } from 'features/Filters/types/slice';
const MockedState = {
// Copied from Redux DevTools from browser
@ -62,30 +64,55 @@ const MockedState = {
},
],
},
filter: {
regionOptions: [],
typeOptions: [],
sortOptions: [],
selectedRegion: 'kanto',
selectedType: 'All Types',
selectedSort: 'id',
searchInput: '',
},
};
interface MockStoreProps {
pokedexState: PokedexStateProps;
filterState: FilterStateProps;
children: React.ReactNode;
}
// Create a mock store
const mockSlice = (pokedexState: PokedexStateProps) => {
const mockPokedexSlice = (pokedexState: PokedexStateProps) => {
return createSlice({
name: 'pokedex',
initialState: pokedexState,
reducers: {},
});
};
const mockStore = (pokedexState: PokedexStateProps) => {
const mockFilterSlice = (filterState: FilterStateProps) => {
return createSlice({
name: 'filter',
initialState: filterState,
reducers: {},
});
};
const mockStore = (
pokedexState: PokedexStateProps,
filterState: FilterStateProps,
) => {
return configureStore({
reducer: {
pokedex: mockSlice(pokedexState).reducer,
filter: mockFilterSlice(filterState).reducer,
pokedex: mockPokedexSlice(pokedexState).reducer,
},
});
};
const Mockstore: React.FC<MockStoreProps> = ({ pokedexState, children }) => (
<Provider store={mockStore(pokedexState)}>{children}</Provider>
const Mockstore: React.FC<MockStoreProps> = ({
pokedexState,
filterState,
children,
}) => (
<Provider store={mockStore(pokedexState, filterState)}>{children}</Provider>
);
const meta: Meta<typeof Pokedex> = {
@ -105,21 +132,53 @@ export default meta;
export const Loding = {
decorators: [
(story: () => React.ReactNode) => (
<Mockstore pokedexState={initialState}>{story()}</Mockstore>
<Mockstore
pokedexState={initialPokedexState}
filterState={initialFilterState}
>
{story()}
</Mockstore>
),
],
};
export const Primary = {
export const All = {
decorators: [
(story: () => React.ReactNode) => (
<Mockstore pokedexState={MockedState.pokedex}>{story()}</Mockstore>
<Mockstore
pokedexState={MockedState.pokedex}
filterState={MockedState.filter}
>
{story()}
</Mockstore>
),
],
args: {
selectedRegion: 'kanto',
},
};
const filterStateOnlyFire = {
regionOptions: [],
typeOptions: [],
sortOptions: [],
selectedRegion: 'kanto',
selectedType: 'fire',
selectedSort: 'id',
searchInput: '',
};
export const typeFireSelected = {
decorators: [
(story: () => React.ReactNode) => (
<Mockstore
pokedexState={MockedState.pokedex}
filterState={filterStateOnlyFire}
>
{story()}
</Mockstore>
),
],
args: {
selectedRegion: 'kanto',
selectedType: 'All Types',
selectedSort: 'id',
searchInput: '',
},
};

View File

@ -3,75 +3,24 @@ import PokemonCard, { PokemonCardProps } from 'components/PokemonCard';
import Loading from 'components/Loading';
import { useAppSelector, useAppDispatch } from 'app/hooks';
import { fetchPokemonsInTheRegion } from './pokedexSlice';
import {
fetchPokemonsInTheRegion,
searchedSortedFilteredPokemonCardList,
} from './pokedexSlice';
import { fetchSelectedPokemonInfo } from 'features/InfoDialog/infoDialogSlice';
export const filterPokemonCardsByType = (
pokemonList: PokemonCardProps[],
selectedType: string,
) => {
return pokemonList.filter(
pokemon =>
selectedType === 'All Types' ||
pokemon.types.some(type => type === selectedType),
);
};
export const sortPokemonCardsByIdOrName = (
pokemonList: PokemonCardProps[],
selectedSort: string,
) => {
return pokemonList.sort((a, b) => {
if (selectedSort === 'id') {
return a.id - b.id;
} else if (selectedSort === 'name') {
return a.name.localeCompare(b.name);
} else {
return 0;
}
});
};
export const searchPokemonCardsByName = (
pokemonList: PokemonCardProps[],
searchInput: string,
) => {
return pokemonList.filter(pokemon =>
pokemon.name.toLowerCase().includes(searchInput.toLowerCase()),
);
};
export interface PokedexProps {
selectedRegion: string;
selectedType: string;
selectedSort: string;
searchInput: string;
}
const Pokedex = ({
selectedRegion,
selectedType,
selectedSort,
searchInput,
}: PokedexProps) => {
const Pokedex = ({ selectedRegion }: PokedexProps) => {
const dispatch = useAppDispatch();
const isLoadingPokemons = useAppSelector(
state => state.pokedex.isLoadingPokemons,
);
const pokemonList = useAppSelector(state => state.pokedex.pokemonCardList);
const filteredPokemonList = filterPokemonCardsByType(
pokemonList,
selectedType,
);
const sortedFilteredPokemonCardList = sortPokemonCardsByIdOrName(
filteredPokemonList,
selectedSort,
);
const searchedPokemonCardList = searchPokemonCardsByName(
sortedFilteredPokemonCardList,
searchInput,
const pokemonCardListForRendering = searchedSortedFilteredPokemonCardList(
useAppSelector(state => state),
);
useEffect(() => {
@ -84,7 +33,7 @@ const Pokedex = ({
<Loading />
) : (
<div className="all__pokemons">
{searchedPokemonCardList.map(pokemonCard => (
{pokemonCardListForRendering.map(pokemonCard => (
<PokemonCard
key={pokemonCard.id}
id={pokemonCard.id}

View File

@ -2,7 +2,7 @@ import {
filterPokemonCardsByType,
sortPokemonCardsByIdOrName,
searchPokemonCardsByName,
} from 'features/Pokedex/Pokedex';
} from 'features/Pokedex/pokedexSlice';
import { PokemonCardProps } from 'components/PokemonCard';
import pokemon3_venusaur_card from 'features/Pokedex/__test__/pokemon3_venusaur_Card.json';
import pokemon4_charmander_card from 'features/Pokedex/__test__/pokemon4_charmandar_Card.json';

View File

@ -7,6 +7,7 @@ import { getStartAndEndIdsForRegion } from './utils';
import { PokemonResponseData } from 'types/api';
import { pokeRestApi } from 'app/services/pokeRestApi';
import { RootState } from 'app/store';
import { PokemonCardProps } from 'components/PokemonCard';
export const fetchPokemonsInTheRegion = createAsyncThunk<
PokemonResponseData[],
@ -66,3 +67,57 @@ export const pokedexSlice: Slice<PokedexStateProps> = createSlice({
export const { setIsLoadingPokemons } = pokedexSlice.actions;
export default pokedexSlice.reducer;
/// selectors
export const filterPokemonCardsByType = (
pokemonList: PokemonCardProps[],
selectedType: string,
) => {
return pokemonList.filter(
pokemon =>
selectedType === 'All Types' ||
pokemon.types.some(type => type === selectedType),
);
};
export const sortPokemonCardsByIdOrName = (
pokemonList: PokemonCardProps[],
selectedSort: string,
) => {
return pokemonList.sort((a, b) => {
if (selectedSort === 'id') {
return a.id - b.id;
} else if (selectedSort === 'name') {
return a.name.localeCompare(b.name);
} else {
return 0;
}
});
};
export const searchPokemonCardsByName = (
pokemonList: PokemonCardProps[],
searchInput: string,
) => {
return pokemonList.filter(pokemon =>
pokemon.name.toLowerCase().includes(searchInput.toLowerCase()),
);
};
export const filteredPokemonListByType = (state: RootState) =>
filterPokemonCardsByType(
state.pokedex.pokemonCardList,
state.filter.selectedType,
);
export const sortedFilteredPokemonCardList = (state: RootState) =>
sortPokemonCardsByIdOrName(
filteredPokemonListByType(state),
state.filter.selectedSort,
);
export const searchedSortedFilteredPokemonCardList = (state: RootState) =>
searchPokemonCardsByName(
sortedFilteredPokemonCardList(state),
state.filter.searchInput,
);