Compare commits
20 Commits
ec0af2f3ac
...
6acabeb91a
Author | SHA1 | Date |
---|---|---|
Jason Zhu | 6acabeb91a | |
Jason Zhu | ff92db699d | |
Jason Zhu | f5e0a75c64 | |
Jason Zhu | 749a745b4a | |
Jason Zhu | 0c4f2d4b67 | |
Jason Zhu | 2ae23cdbfa | |
Jason Zhu | 1f0ab75d6e | |
Jason Zhu | 93fe0cb24a | |
Jason Zhu | 40a64e9033 | |
Jason Zhu | a21427b91f | |
Jason Zhu | be9d767bfd | |
Jason Zhu | 6295a6a792 | |
Jason Zhu | dbb7ab99d2 | |
Jason Zhu | 92d3428c84 | |
Jason Zhu | ce3d3ce0d7 | |
Jason Zhu | 5068a6f728 | |
Jason Zhu | b56eae4e40 | |
Jason Zhu | b2839de8a7 | |
Jason Zhu | 133884e717 | |
Jason Zhu | bb5f6d73c4 |
|
@ -1,3 +1,5 @@
|
|||
import 'index.css';
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react';
|
||||
import './App.css';
|
||||
import Header from 'components/Header';
|
||||
import Pokedex from './features/Pokedex';
|
||||
import Pokedex from 'features/Pokedex';
|
||||
import Filters from 'features/Filters';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App app_container">
|
||||
<Header />
|
||||
<Filters />
|
||||
<Pokedex />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { listenerMiddleware } from './listenerMiddleware';
|
||||
import { pokedexApi } from 'features/Pokedex/pokedexApi';
|
||||
import { pokedexSlice } from 'features/Pokedex/pokedexSlice';
|
||||
import { filterSlice } from 'features/Filters/filterSlice';
|
||||
import { filterApi } from 'features/Filters/filterApi';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
// component slices
|
||||
pokedex: pokedexSlice.reducer,
|
||||
filter: filterSlice.reducer,
|
||||
|
||||
// api
|
||||
[pokedexApi.reducerPath]: pokedexApi.reducer,
|
||||
// api slices
|
||||
[filterApi.reducerPath]: filterApi.reducer,
|
||||
},
|
||||
middleware: getDefaultMiddleware =>
|
||||
getDefaultMiddleware().concat(
|
||||
pokedexApi.middleware,
|
||||
filterApi.middleware,
|
||||
listenerMiddleware.middleware,
|
||||
),
|
||||
devTools: true,
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
.fa-mars {
|
||||
color: #7070ff;
|
||||
}
|
||||
|
||||
.fa-venus {
|
||||
color: rgb(224, 61, 88);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import GenderRate, { GenderRateProps } from './GenderRate';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
export default {
|
||||
title: 'Component/GenderRate',
|
||||
component: GenderRate,
|
||||
} as ComponentMeta<typeof GenderRate>;
|
||||
|
||||
const Template: ComponentStory<typeof GenderRate> = (args: GenderRateProps) => (
|
||||
<GenderRate {...args} />
|
||||
);
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
|
||||
Primary.args = {
|
||||
genderRatio: 0,
|
||||
};
|
||||
|
||||
export const Option1 = Template.bind({});
|
||||
Option1.args = {
|
||||
genderRatio: 1,
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
import './GenderRate.css';
|
||||
|
||||
export interface GenderRateProps {
|
||||
genderRatio: number;
|
||||
}
|
||||
|
||||
const GenderRate = ({ genderRatio }: GenderRateProps) => {
|
||||
switch (genderRatio) {
|
||||
case 0:
|
||||
return (
|
||||
<div>
|
||||
<span className="gender-male">
|
||||
100% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
0% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
87.5% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
12.5% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
75% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
25% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
62.5% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
37.5% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
50% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
50% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
37.5% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
62.5% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
25% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
75% <i className="fa fa-venus">♀</i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 7:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
12.5% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
87.5% <i className="fa fa-venus"></i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case 8:
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
0% <i className="fa fa-mars">♂</i>
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
100% <i className="fa fa-venus"></i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <span>Loading...</span>;
|
||||
}
|
||||
};
|
||||
|
||||
export default GenderRate;
|
|
@ -0,0 +1 @@
|
|||
export { default } from './GenderRate';
|
|
@ -28,7 +28,7 @@ html {
|
|||
|
||||
@font-face {
|
||||
font-family: 'Press Start 2P';
|
||||
src: url('../../../assets/PressStart2P-Regular.ttf') format('truetype');
|
||||
src: url('assets/PressStart2P-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.thumbnail__container {
|
||||
|
@ -40,7 +40,7 @@ html {
|
|||
margin: 2rem;
|
||||
/* border: 15px solid var(--cardborder); */
|
||||
border-radius: 1rem;
|
||||
min-width: 220px;
|
||||
width: 220px;
|
||||
height: 285px;
|
||||
text-align: center;
|
||||
/* box-shadow: 0 5px 25px 1px rgb(0 0 0 / 50%); */
|
||||
|
@ -96,6 +96,30 @@ h3 {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.poke__type {
|
||||
display: flex;
|
||||
grid-gap: 0 10px;
|
||||
gap: 0 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.poke__type__bg > img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.poke__type__bg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.grass {
|
||||
background: var(--grass);
|
||||
box-shadow: 0 0 20px var(--grass);
|
|
@ -8,7 +8,7 @@ import charizard_svg from './assets/stories/charizard.svg';
|
|||
import charizard_info from './assets/stories/charizard.json';
|
||||
|
||||
export default {
|
||||
title: 'Pokedex/PokemonCard',
|
||||
title: 'Component/PokemonCard',
|
||||
component: PokemonCard,
|
||||
} as ComponentMeta<typeof PokemonCard>;
|
||||
|
|
@ -17,12 +17,7 @@ export function formatNumber(num: number) {
|
|||
return '#' + num.toString().padStart(3, '0');
|
||||
}
|
||||
|
||||
export default function PokemonCard({
|
||||
id,
|
||||
name,
|
||||
image,
|
||||
types,
|
||||
}: PokemonCardProps) {
|
||||
const PokemonCard = ({ id, name, image, types }: PokemonCardProps) => {
|
||||
let finalColor;
|
||||
|
||||
if (types.length === 2) {
|
||||
|
@ -67,8 +62,12 @@ export default function PokemonCard({
|
|||
</div>
|
||||
<div className="poke__name">
|
||||
<h3>{name}</h3>
|
||||
<div className="poke__type">
|
||||
<PokemonTypes types={types} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default PokemonCard;
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
@ -5,7 +5,7 @@ import { Tooltip, Zoom } from '@mui/material';
|
|||
import * as pokeTypeAsset from 'assets/types';
|
||||
import './PokemonTypes.css';
|
||||
|
||||
function findPokeTypeAsset(pokeType: string) {
|
||||
export const findPokeTypeAsset = (pokeType: string) => {
|
||||
switch (pokeType) {
|
||||
case 'normal':
|
||||
return pokeTypeAsset.pokeType_normal;
|
||||
|
@ -46,7 +46,7 @@ function findPokeTypeAsset(pokeType: string) {
|
|||
default:
|
||||
return pokeTypeAsset.pokeType_normal;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface PokemonTypesProps {
|
||||
types: string[];
|
||||
|
@ -54,7 +54,8 @@ export interface PokemonTypesProps {
|
|||
|
||||
const PokemonTypes = ({ types }: PokemonTypesProps) => {
|
||||
return (
|
||||
<div className="poke__type">
|
||||
// css is set in consumer
|
||||
<>
|
||||
{types.map(type => (
|
||||
<Tooltip title={type} key={type} TransitionComponent={Zoom} arrow>
|
||||
<div className={`poke__type__bg ${type}`}>
|
||||
|
@ -62,7 +63,7 @@ const PokemonTypes = ({ types }: PokemonTypesProps) => {
|
|||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './PokemonTypes';
|
||||
export { default } from './PokemonTypes';
|
|
@ -0,0 +1,42 @@
|
|||
html {
|
||||
--toggle: hsl(230deg 17% 85%);
|
||||
--bgcolor: none;
|
||||
--colorPrimary: #000;
|
||||
--filterHeading: #000;
|
||||
--selectBg: #fff;
|
||||
--selectText: #000;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'barcadebrawl';
|
||||
src: url('assets/fonts/BarcadeBrawl.ttf');
|
||||
}
|
||||
|
||||
.filter__container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 7vh 0 5vh 0;
|
||||
gap: 0 2vw;
|
||||
}
|
||||
|
||||
.filter__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: 'barcadebrawl';
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* TODO: Fix incorrect render of input */
|
||||
.filter__items > input {
|
||||
width: 10vw;
|
||||
margin-top: 5px;
|
||||
background-color: var(--selectBg);
|
||||
color: var(--selectText);
|
||||
border: 1px solid var(--selectText);
|
||||
}
|
||||
|
||||
.filter__items > div,
|
||||
label {
|
||||
color: var(--filterHeading);
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useGetTypeListQuery } from 'features/Pokedex/pokedexApi';
|
||||
import { useAppDispatch, useAppSelector } from 'app/hooks';
|
||||
import { fetchPokemonsInTheRegion } from 'features/Pokedex/pokedexSlice';
|
||||
|
||||
import {
|
||||
setSelectedRegion,
|
||||
setSelectedType,
|
||||
setSelectedSort,
|
||||
fetchPokemonsInTheRegion,
|
||||
setRegionOptions,
|
||||
setSortOptions,
|
||||
setTypeOptions,
|
||||
setSortOptions,
|
||||
setSearchInput,
|
||||
} from 'features/Pokedex/pokedexSlice';
|
||||
import { RegionPokemonRange } from 'features/Pokedex/types/slice';
|
||||
import { useAppDispatch, useAppSelector } from 'app/hooks';
|
||||
} from './filterSlice';
|
||||
import { useGetTypeListQuery } from './filterApi';
|
||||
import { RegionPokemonRange } from './types/slice';
|
||||
import './Filters.css';
|
||||
|
||||
export const createRegionPokemonListOptionElements = (
|
||||
data: RegionPokemonRange[],
|
||||
|
@ -54,13 +56,12 @@ const useGetSortOptions = () => {
|
|||
|
||||
const Filters = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedRegion = useAppSelector(state => state.pokedex.selectedRegion);
|
||||
const selectedType = useAppSelector(state => state.pokedex.selectedType);
|
||||
const selectedSort = useAppSelector(state => state.pokedex.selectedSort);
|
||||
const selectedRegion = useAppSelector(state => state.filter.selectedRegion);
|
||||
const selectedType = useAppSelector(state => state.filter.selectedType);
|
||||
const selectedSort = useAppSelector(state => state.filter.selectedSort);
|
||||
const searchInput = useAppSelector(state => state.filter.searchInput);
|
||||
|
||||
const regionPokemonList = useAppSelector(
|
||||
state => state.pokedex.regionOptions,
|
||||
);
|
||||
const regionPokemonList = useAppSelector(state => state.filter.regionOptions);
|
||||
|
||||
const { data: fetchedRegionOptions } = useGetRegionOptions();
|
||||
const { data: fetchedTypeOptions, isLoading: isFetchingTypeOptions } =
|
||||
|
@ -89,7 +90,7 @@ const Filters = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="filter__container">
|
||||
<div className="filter__container noselect">
|
||||
<div className="filter__items">
|
||||
<div>
|
||||
<div>REGION</div>
|
||||
|
@ -148,6 +149,7 @@ const Filters = () => {
|
|||
<input
|
||||
type="text"
|
||||
onChange={e => dispatch(setSearchInput(e.target.value))}
|
||||
value={searchInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import { pokeApiBaseQuery } from '../../services/pokeapi/paginationBaseQuery';
|
||||
import {
|
||||
RegionListResponseData,
|
||||
TypeListResponseData,
|
||||
} from '../Pokedex/types/api';
|
||||
|
||||
export const filterApi = createApi({
|
||||
reducerPath: 'filterApi',
|
||||
baseQuery: pokeApiBaseQuery,
|
||||
endpoints: builder => ({
|
||||
getTypeList: builder.query<TypeListResponseData, void>({
|
||||
query: () => ({ url: 'type', fetchAllPages: true }),
|
||||
transformResponse: (response: RegionListResponseData) => {
|
||||
return {
|
||||
...response,
|
||||
results: [{ name: 'All Types', url: '' }, ...response.results],
|
||||
};
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetTypeListQuery } = filterApi;
|
|
@ -0,0 +1,72 @@
|
|||
import { createSlice, PayloadAction, Slice } from '@reduxjs/toolkit';
|
||||
import { FilterState } from './types/slice';
|
||||
import { RegionPokemonRange } from './types/slice';
|
||||
import { filterApi } from './filterApi';
|
||||
|
||||
filterApi.endpoints.getTypeList.initiate(); // initialize type list fetching
|
||||
|
||||
const initialState: FilterState = {
|
||||
regionOptions: [],
|
||||
typeOptions: [],
|
||||
sortOptions: [],
|
||||
selectedRegion: '',
|
||||
selectedType: '',
|
||||
selectedSort: '',
|
||||
searchInput: '',
|
||||
};
|
||||
|
||||
export const filterSlice: Slice<FilterState> = createSlice({
|
||||
name: 'filter',
|
||||
initialState,
|
||||
reducers: {
|
||||
setSelectedRegion: (state, action: PayloadAction<string>) => {
|
||||
state.selectedRegion = action.payload;
|
||||
},
|
||||
setSelectedType: (state, action: PayloadAction<string>) => {
|
||||
state.selectedType = action.payload;
|
||||
},
|
||||
setSelectedSort: (state, action: PayloadAction<string>) => {
|
||||
state.selectedSort = action.payload;
|
||||
},
|
||||
setRegionOptions: (state, action: PayloadAction<RegionPokemonRange[]>) => {
|
||||
state.regionOptions = action.payload;
|
||||
},
|
||||
setTypeOptions: (state, action: PayloadAction<string[]>) => {
|
||||
state.typeOptions = action.payload;
|
||||
},
|
||||
setSortOptions: (
|
||||
state,
|
||||
action: PayloadAction<{ name: string; value: string }[]>,
|
||||
) => {
|
||||
state.sortOptions = action.payload;
|
||||
},
|
||||
setSearchInput: (state, action: PayloadAction<string>) => {
|
||||
state.searchInput = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: builder => {
|
||||
builder.addMatcher(
|
||||
filterApi.endpoints.getTypeList.matchFulfilled,
|
||||
(state, action) => {
|
||||
if (action.payload && action.payload.results.length > 0) {
|
||||
const regionListResults = action.payload.results;
|
||||
state.typeOptions = regionListResults.map(region => region.name);
|
||||
|
||||
state.selectedType = action.payload.results[0].name;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setSelectedRegion,
|
||||
setSelectedType,
|
||||
setSelectedSort,
|
||||
setRegionOptions,
|
||||
setTypeOptions,
|
||||
setSortOptions,
|
||||
setSearchInput,
|
||||
} = filterSlice.actions;
|
||||
|
||||
export default filterSlice.reducer;
|
|
@ -0,0 +1 @@
|
|||
export { default } from './Filters';
|
|
@ -0,0 +1,15 @@
|
|||
export type FilterState = {
|
||||
regionOptions: RegionPokemonRange[];
|
||||
typeOptions: string[];
|
||||
sortOptions: { name: string; value: string }[];
|
||||
selectedRegion: string;
|
||||
selectedType: string;
|
||||
selectedSort: string;
|
||||
searchInput: string;
|
||||
};
|
||||
|
||||
export type RegionPokemonRange = {
|
||||
region: string;
|
||||
startId: number;
|
||||
endId: number;
|
||||
};
|
|
@ -0,0 +1,350 @@
|
|||
html {
|
||||
--pokename: #000;
|
||||
--cardborder: #fff;
|
||||
--pokenumber: hsl(228, 28%, 20%);
|
||||
--info: #fff;
|
||||
--bggradient: url('assets/bg.png');
|
||||
--bgcolor: none;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
--pokename: hsl(228, 28%, 20%);
|
||||
--cardborder: hsl(228, 28%, 20%);
|
||||
--pokenumber: hsl(228, 28%, 20%);
|
||||
--info: hsl(228, 28%, 20%);
|
||||
--bggradient: none;
|
||||
--bgcolor: #16171f;
|
||||
}
|
||||
|
||||
.info__container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.info__container__data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.info__container__img {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
border-radius: 1rem;
|
||||
background: #ffffff78;
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
.info__container__img img {
|
||||
width: 120px;
|
||||
height: 147px;
|
||||
padding: 1rem 1rem;
|
||||
margin: 0.3rem;
|
||||
border-radius: 1.2rem;
|
||||
}
|
||||
|
||||
.pokemon__name {
|
||||
font-size: 22px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pokemon__id {
|
||||
color: var(--pokenumber);
|
||||
font-family: 'Teko', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.info__container__data__header {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info__container__data__type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info__container__data__type img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.info__container__headings {
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 27px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info__container__data__abilities {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info__container__data__data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
grid-gap: 0px 15px;
|
||||
gap: 0px 15px;
|
||||
align-items: center;
|
||||
margin: 0px 0;
|
||||
justify-content: space-between;
|
||||
background-color: #ffffff40;
|
||||
padding: 10px;
|
||||
border-radius: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.info__container__stat__columns {
|
||||
width: 135px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.MuiDialog-paperFullWidth {
|
||||
width: 63%;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 10px 20px rgba(175, 136, 136, 0.29),
|
||||
0 6px 6px rgba(0, 0, 0, 0.43);
|
||||
}
|
||||
|
||||
.MuiDialog-container {
|
||||
background-color: var(--bgcolor);
|
||||
background-image: var(--bggradient);
|
||||
}
|
||||
|
||||
.MuiBackdrop-root {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.info__container__data__dimensions {
|
||||
display: flex;
|
||||
gap: 0 30px;
|
||||
}
|
||||
|
||||
.info__container__stat__columns__name {
|
||||
text-transform: uppercase;
|
||||
color: crimson;
|
||||
}
|
||||
|
||||
.dialog__bg {
|
||||
background-image: url('assets/bg.png');
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.dialog__content {
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.pokemon__name {
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
}
|
||||
|
||||
.close__btn {
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
.ability {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.right__box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: -webkit-fill-available;
|
||||
justify-content: space-evenly;
|
||||
gap: 3px 0px;
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.evolution__box {
|
||||
display: flex;
|
||||
gap: 0 0px;
|
||||
}
|
||||
|
||||
.evolution__sub__box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.evolution__box .evo_img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* .evolution__img__div:hover {
|
||||
transform: scale(1.05);
|
||||
} */
|
||||
|
||||
.evolution__img__div {
|
||||
height: 113px;
|
||||
width: 113px;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 5px 15px 4px rgb(0 0 0 / 30%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.35s;
|
||||
margin-top: 3vh;
|
||||
}
|
||||
|
||||
.evolution__poke__name {
|
||||
text-transform: capitalize;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.arrow__right {
|
||||
font-size: 1.75rem;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.MuiDialogContent-root:first-child {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ability__list {
|
||||
display: flex;
|
||||
gap: 0 30px;
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
.ability__list__bg {
|
||||
background-color: #ffffff40;
|
||||
padding: 5px;
|
||||
border-radius: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.MuiTooltip-tooltip {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
height: 80;
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
rgb(77, 77, 77),
|
||||
rgb(77, 77, 77)
|
||||
);
|
||||
width: 1%;
|
||||
margin-left: 2vw;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.transparency__div {
|
||||
background: #ffffff40;
|
||||
height: 113px;
|
||||
width: 113px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.desc {
|
||||
background-color: #ffffff40;
|
||||
padding: 10px;
|
||||
border-radius: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.fa-mars {
|
||||
color: #7070ff;
|
||||
}
|
||||
|
||||
.fa-venus {
|
||||
color: rgb(224, 61, 88);
|
||||
}
|
||||
|
||||
.dimensions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pokemon__genera {
|
||||
padding: 3px 10px;
|
||||
border-radius: 10px;
|
||||
margin-top: 1.5vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* phone */
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.MuiDialog-paperFullWidth {
|
||||
width: 75%;
|
||||
height: 100%;
|
||||
}
|
||||
.info__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.info__container__img {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.info__container__data {
|
||||
margin-left: 0px;
|
||||
}
|
||||
.right__box {
|
||||
margin-top: 20px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.info__container__data__data {
|
||||
justify-content: center;
|
||||
}
|
||||
.info__container__stat__columns__name,
|
||||
.info__container__stat__columns__val,
|
||||
.stats,
|
||||
.dimensions,
|
||||
.ability {
|
||||
text-align: center;
|
||||
}
|
||||
.info__container__data__abilities {
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
gap: 0px;
|
||||
}
|
||||
.right__box .desc {
|
||||
text-align: center;
|
||||
}
|
||||
.info__container__headings {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.evolution__box {
|
||||
flex-direction: column;
|
||||
}
|
||||
.evolution__sub__box {
|
||||
flex-direction: column;
|
||||
}
|
||||
.arrow__right {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.evolution__poke__name {
|
||||
margin: 12px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import InfoDialog, { InfoDialogProps } from './InfoDialog';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
export default {
|
||||
title: 'Features/InfoDialog',
|
||||
component: InfoDialog,
|
||||
} as ComponentMeta<typeof InfoDialog>;
|
||||
|
||||
const Template: ComponentStory<typeof InfoDialog> = (args: InfoDialogProps) => (
|
||||
<InfoDialog {...args} />
|
||||
);
|
|
@ -0,0 +1,132 @@
|
|||
import { Dialog, DialogContent, Tooltip, Zoom } from '@mui/material';
|
||||
|
||||
import { findPokeTypeAsset } from 'components/PokemonTypes';
|
||||
import { colorTypeGradients } from 'components/PokemonCard/utils';
|
||||
import GenderRate from 'components/GenderRate';
|
||||
|
||||
export interface InfoDialogProps {
|
||||
open: boolean;
|
||||
cancel: boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
genere: string;
|
||||
types: string[];
|
||||
height: number;
|
||||
weight: number;
|
||||
genderRatio: number;
|
||||
description: string;
|
||||
abilities: string[];
|
||||
stats: {
|
||||
hp: number;
|
||||
attack: number;
|
||||
defense: number;
|
||||
spAttack: number;
|
||||
spDefense: number;
|
||||
speed: number;
|
||||
};
|
||||
image: string;
|
||||
}
|
||||
|
||||
const InfoDialog = (props: InfoDialogProps) => {
|
||||
let finalColor;
|
||||
|
||||
if (props.types.length === 2) {
|
||||
finalColor = colorTypeGradients(
|
||||
props.types[0],
|
||||
props.types[1],
|
||||
props.types.length,
|
||||
);
|
||||
} else {
|
||||
finalColor = colorTypeGradients(
|
||||
props.types[0],
|
||||
props.types[0],
|
||||
props.types.length,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
aria-labelledby="customized-dialog-title"
|
||||
open={props.open}
|
||||
fullWidth
|
||||
maxWidth="md"
|
||||
className="dialog__bg noselect"
|
||||
>
|
||||
<DialogContent
|
||||
style={{
|
||||
background: `linear-gradient(${finalColor[0]}, ${finalColor[1]})`,
|
||||
}}
|
||||
className="dialog__content"
|
||||
>
|
||||
<div className="info__container">
|
||||
<div className="info__container__img">
|
||||
<div className="pokemon__id">
|
||||
#{String(props.id).padStart(3, '0')}
|
||||
</div>
|
||||
<div className="pokemon__name">{props.name}</div>
|
||||
<div
|
||||
className="pokemon__genera"
|
||||
style={{ background: finalColor[0] }}
|
||||
>
|
||||
{props.genere}
|
||||
</div>
|
||||
<div>
|
||||
<img src={props.image} alt="poke-img" />
|
||||
</div>
|
||||
<div className="info__container__data__type">
|
||||
{props.types.map(type => (
|
||||
<Tooltip
|
||||
title={type}
|
||||
key={type}
|
||||
TransitionComponent={Zoom}
|
||||
arrow
|
||||
>
|
||||
<div className={`poke__type__bg ${type}`}>
|
||||
<img src={findPokeTypeAsset(type)} alt="poke-type" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
<div className="dimensions">
|
||||
<p>
|
||||
<span
|
||||
className="info__container__headings"
|
||||
style={{ fontSize: '20px' }}
|
||||
>
|
||||
Height
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
className="info__container__headings"
|
||||
style={{ fontSize: '20px' }}
|
||||
>
|
||||
Weight
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="gender__container">
|
||||
{props.genderRatio === -1 ? (
|
||||
'Genderless'
|
||||
) : (
|
||||
<GenderRate genderRatio={props.genderRatio} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info__container__data">
|
||||
<div className={'right__box'}>
|
||||
<div>
|
||||
<div className={'info__container__headings'}>About</div>
|
||||
<div className={'desc'}>{props.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export * from './InfoDialog';
|
||||
export { default } from './InfoDialog';
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from './Filters';
|
||||
export { default as useFilterLoaded } from './useFilterLoaded';
|
|
@ -1,18 +0,0 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useGetRegionListQuery, useGetTypeListQuery } from '../pokedexApi';
|
||||
|
||||
const useFilterLoaded = () => {
|
||||
const { isLoading: isLoadingRegionList } = useGetRegionListQuery();
|
||||
const { isLoading: isLoadingTypeList } = useGetTypeListQuery();
|
||||
const [isFilterLoaded, setIsFilterLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingRegionList && !isLoadingTypeList) {
|
||||
setIsFilterLoaded(true);
|
||||
}
|
||||
}, [isLoadingRegionList, isLoadingTypeList]);
|
||||
|
||||
return isFilterLoaded;
|
||||
};
|
||||
|
||||
export default useFilterLoaded;
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import PokemonCard, { PokemonCardProps } from './PokemonCard';
|
||||
import Filters from './Filters';
|
||||
import PokemonCard, { PokemonCardProps } from 'components/PokemonCard';
|
||||
import Loading from 'components/Loading';
|
||||
|
||||
import { useAppSelector } from 'app/hooks';
|
||||
|
@ -41,13 +40,13 @@ export const searchPokemonCardsByName = (
|
|||
};
|
||||
|
||||
const Pokedex = () => {
|
||||
const selectedType = useAppSelector(state => state.filter.selectedType);
|
||||
const selectedSort = useAppSelector(state => state.filter.selectedSort);
|
||||
const searchInput = useAppSelector(state => state.filter.searchInput);
|
||||
|
||||
const isLoadingPokemons = useAppSelector(
|
||||
state => state.pokedex.isLoadingPokemons,
|
||||
);
|
||||
const selectedType = useAppSelector(state => state.pokedex.selectedType);
|
||||
const selectedSort = useAppSelector(state => state.pokedex.selectedSort);
|
||||
const searchInput = useAppSelector(state => state.pokedex.searchInput);
|
||||
|
||||
const pokemonList = useAppSelector(state => state.pokedex.pokemonCardList);
|
||||
|
||||
const filteredPokemonList = filterPokemonCardsByType(
|
||||
|
@ -65,7 +64,6 @@ const Pokedex = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Filters />
|
||||
{isLoadingPokemons ? (
|
||||
<Loading />
|
||||
) : (
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
import {
|
||||
pokeApiAllPagesCustomBaseQuery,
|
||||
pokeApiFullListFetchArgs,
|
||||
} from './paginationBaseQuery';
|
||||
import {
|
||||
AreaResponseData,
|
||||
LocationResponseData,
|
||||
PokemonListResponseData,
|
||||
PokemonResponseData,
|
||||
RegionListResponseData,
|
||||
RegionResponseData,
|
||||
TypeListResponseData,
|
||||
TypeResponseData,
|
||||
PokemonListItem,
|
||||
nameUrlPair,
|
||||
PokemonList,
|
||||
} from './types/api';
|
||||
|
||||
const pokeApiBaseQuery = async (
|
||||
args: pokeApiFullListFetchArgs,
|
||||
api: any,
|
||||
extra: any,
|
||||
) => {
|
||||
const baseUrl = 'https://pokeapi.co/api/v2/';
|
||||
|
||||
if (args.fetchAllPages) {
|
||||
return pokeApiAllPagesCustomBaseQuery(args, api, extra, baseUrl);
|
||||
} else {
|
||||
return fetchBaseQuery({ baseUrl })(args, api, extra);
|
||||
}
|
||||
};
|
||||
|
||||
export const pokedexApi = createApi({
|
||||
reducerPath: 'pokedexApi',
|
||||
baseQuery: pokeApiBaseQuery,
|
||||
endpoints: builder => ({
|
||||
getPokemonList: builder.query<PokemonListResponseData, void>({
|
||||
query: () => ({ url: `pokemon`, fetchAllPages: true }),
|
||||
}),
|
||||
getRegionList: builder.query<RegionListResponseData, void>({
|
||||
query: () => ({ url: 'region', fetchAllPages: true }),
|
||||
}),
|
||||
getTypeList: builder.query<TypeListResponseData, void>({
|
||||
query: () => ({ url: 'type', fetchAllPages: true }),
|
||||
transformResponse: (response: RegionListResponseData) => {
|
||||
return {
|
||||
...response,
|
||||
results: [{ name: 'All Types', url: '' }, ...response.results],
|
||||
};
|
||||
},
|
||||
}),
|
||||
getPokemon: builder.query<PokemonResponseData, number | string>({
|
||||
query: IdOrName => ({ url: `pokemon/${IdOrName}` }),
|
||||
}),
|
||||
getRegion: builder.query<RegionResponseData, number | string>({
|
||||
query: IdOrName => ({ url: `region/${IdOrName}` }),
|
||||
}),
|
||||
getType: builder.query<TypeResponseData, number | string>({
|
||||
query: IdOrName => ({ url: `type/${IdOrName}` }),
|
||||
}),
|
||||
getLocation: builder.query<LocationResponseData, number | string>({
|
||||
query: IdOrName => ({ url: `location/${IdOrName}` }),
|
||||
}),
|
||||
getArea: builder.query<AreaResponseData, number | string>({
|
||||
query: IdOrName => ({ url: `location-area/${IdOrName}` }),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetPokemonListQuery,
|
||||
useGetRegionListQuery,
|
||||
useGetTypeListQuery,
|
||||
useGetPokemonQuery,
|
||||
useGetRegionQuery,
|
||||
useGetTypeQuery,
|
||||
useGetAreaQuery,
|
||||
useGetLocationQuery,
|
||||
} = pokedexApi;
|
|
@ -1,15 +1,11 @@
|
|||
import { createAsyncThunk, createSlice, Dispatch } from '@reduxjs/toolkit';
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
import type { Slice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { PokedexState, RegionPokemonRange } from 'features/Pokedex/types/slice';
|
||||
import { PokedexState } from 'features/Pokedex/types/slice';
|
||||
|
||||
import { getStartAndEndIdsForRegion } from './utils';
|
||||
import { PokemonResponseData } from './types/api';
|
||||
import { pokedexApi } from './pokedexApi';
|
||||
import { RootState } from '../../app/store';
|
||||
|
||||
pokedexApi.endpoints.getTypeList.initiate(); // initialize type list fetching
|
||||
// typesData will be used in Filters.tsx
|
||||
import { RootState } from 'app/store';
|
||||
|
||||
export const fetchPokemonsInTheRegion = createAsyncThunk<
|
||||
PokemonResponseData[],
|
||||
|
@ -17,7 +13,7 @@ export const fetchPokemonsInTheRegion = createAsyncThunk<
|
|||
{ state: RootState }
|
||||
>('pokedex/setSelectedRegion', async (region: string, thunkAPI) => {
|
||||
const { dispatch, getState } = thunkAPI;
|
||||
const regionOptions = getState().pokedex.regionOptions;
|
||||
const regionOptions = getState().filter.regionOptions;
|
||||
|
||||
dispatch(setIsLoadingPokemons(true));
|
||||
|
||||
|
@ -44,13 +40,6 @@ export const fetchPokemonsInTheRegion = createAsyncThunk<
|
|||
});
|
||||
|
||||
const initialState: PokedexState = {
|
||||
regionOptions: [],
|
||||
typeOptions: [],
|
||||
sortOptions: [],
|
||||
selectedRegion: '',
|
||||
selectedType: '',
|
||||
selectedSort: '',
|
||||
searchInput: '',
|
||||
isLoadingPokemons: true,
|
||||
pokemonCardList: [],
|
||||
};
|
||||
|
@ -59,30 +48,6 @@ export const pokedexSlice: Slice<PokedexState> = createSlice({
|
|||
name: 'pokedex',
|
||||
initialState,
|
||||
reducers: {
|
||||
setSelectedRegion: (state, action: PayloadAction<string>) => {
|
||||
state.selectedRegion = action.payload;
|
||||
},
|
||||
setSelectedType: (state, action: PayloadAction<string>) => {
|
||||
state.selectedType = action.payload;
|
||||
},
|
||||
setSelectedSort: (state, action: PayloadAction<string>) => {
|
||||
state.selectedSort = action.payload;
|
||||
},
|
||||
setRegionOptions: (state, action: PayloadAction<RegionPokemonRange[]>) => {
|
||||
state.regionOptions = action.payload;
|
||||
},
|
||||
setTypeOptions: (state, action: PayloadAction<string[]>) => {
|
||||
state.typeOptions = action.payload;
|
||||
},
|
||||
setSortOptions: (
|
||||
state,
|
||||
action: PayloadAction<{ name: string; value: string }[]>,
|
||||
) => {
|
||||
state.sortOptions = action.payload;
|
||||
},
|
||||
setSearchInput: (state, action: PayloadAction<string>) => {
|
||||
state.searchInput = action.payload;
|
||||
},
|
||||
setIsLoadingPokemons: (state, action: PayloadAction<boolean>) => {
|
||||
state.isLoadingPokemons = action.payload;
|
||||
},
|
||||
|
@ -101,29 +66,9 @@ export const pokedexSlice: Slice<PokedexState> = createSlice({
|
|||
types: pokemon.types.map(type => type.type.name),
|
||||
}));
|
||||
});
|
||||
builder.addMatcher(
|
||||
pokedexApi.endpoints.getTypeList.matchFulfilled,
|
||||
(state, action) => {
|
||||
if (action.payload && action.payload.results.length > 0) {
|
||||
const regionListResults = action.payload.results;
|
||||
state.typeOptions = regionListResults.map(region => region.name);
|
||||
|
||||
state.selectedType = action.payload.results[0].name;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setSelectedRegion,
|
||||
setSelectedType,
|
||||
setSelectedSort,
|
||||
setRegionOptions,
|
||||
setTypeOptions,
|
||||
setSortOptions,
|
||||
setSearchInput,
|
||||
setIsLoadingPokemons,
|
||||
} = pokedexSlice.actions;
|
||||
export const { setIsLoadingPokemons } = pokedexSlice.actions;
|
||||
|
||||
export default pokedexSlice.reducer;
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
import { PokemonResponseData } from './api';
|
||||
import { PokemonCardProps } from '../PokemonCard';
|
||||
import { PokemonCardProps } from 'components/PokemonCard';
|
||||
|
||||
export type PokedexState = {
|
||||
regionOptions: RegionPokemonRange[];
|
||||
typeOptions: string[];
|
||||
sortOptions: { name: string; value: string }[];
|
||||
selectedRegion: string;
|
||||
selectedType: string;
|
||||
selectedSort: string;
|
||||
searchInput: string;
|
||||
isLoadingPokemons: boolean;
|
||||
pokemonCardList: PokemonCardProps[];
|
||||
};
|
||||
|
||||
export type RegionPokemonRange = {
|
||||
region: string;
|
||||
startId: number;
|
||||
endId: number;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { RegionPokemonRange } from './types/slice';
|
||||
import { RegionPokemonRange } from 'features/Filters/types/slice';
|
||||
|
||||
export const getStartAndEndIdsForRegion = (
|
||||
region: string,
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
html {
|
||||
--toggle: hsl(230deg 17% 85%);
|
||||
--bggradient: url('assets/bg.png');
|
||||
--bgcolor: none;
|
||||
--colorPrimary: #000;
|
||||
--filterHeading: #000;
|
||||
--selectBg: #fff;
|
||||
--selectText: #000;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
--toggle: linear-gradient(90deg, hsl(216deg 52% 48%), hsl(51deg 100% 60%));
|
||||
--bggradient: none;
|
||||
--bgcolor: #16171f;
|
||||
--colorPrimary: #fff;
|
||||
--filterHeading: #707384;
|
||||
--selectBg: #16171f;
|
||||
--selectText: #707384;
|
||||
}
|
||||
|
||||
.app_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -6,6 +26,11 @@
|
|||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'barcadebrawl';
|
||||
src: url('assets/fonts/BarcadeBrawl.ttf');
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
@ -13,7 +38,7 @@ body {
|
|||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-image: url('./assets/bg.png');
|
||||
background-image: url('assets/bg.png');
|
||||
background-size: initial;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
@ -23,21 +48,6 @@ code {
|
|||
monospace;
|
||||
}
|
||||
|
||||
.filter__container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 7vh 0 5vh 0;
|
||||
gap: 0 2vw;
|
||||
}
|
||||
|
||||
.filter__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: 'barcadebrawl';
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pokemon__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -55,3 +55,17 @@ export const pokeApiAllPagesCustomBaseQuery = async (
|
|||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const pokeApiBaseQuery = async (
|
||||
args: pokeApiFullListFetchArgs,
|
||||
api: any,
|
||||
extra: any,
|
||||
) => {
|
||||
const baseUrl = 'https://pokeapi.co/api/v2/';
|
||||
|
||||
if (args.fetchAllPages) {
|
||||
return pokeApiAllPagesCustomBaseQuery(args, api, extra, baseUrl);
|
||||
} else {
|
||||
return fetchBaseQuery({ baseUrl })(args, api, extra);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue