+
+
{pokemon?.stats.attack}K
+
Attack
-
-
{pokemon?.stats.specialAttack}K
-
Special
+
+
{pokemon?.stats.specialAttack}K
+
Special
-
-
{pokemon?.stats.defense}K
-
Defense
+
+
{pokemon?.stats.defense}K
+
Defense
+ ) : (
+ <>No pokemon selected>
);
};
-export default PokemonCard;
+export default Card;
diff --git a/src/components/card/card.scss b/src/components/card/card.scss
index b9866f6..209c54c 100644
--- a/src/components/card/card.scss
+++ b/src/components/card/card.scss
@@ -1,43 +1,77 @@
-.card {
+.card,
+.card-loading,
+.card-empty {
height: 400px;
width: 300px;
- border: 1px solid black;
- border-radius: 5px;
- background-color: white;
+ border: 2px solid black;
+ border-radius: 8px;
+ background-color: #FFFCF3;
+ box-shadow: 0px 4px 0px black;
+
+ &-empty {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
&-header {
height: 30%;
- background-color: yellow;
+ background-color: #FFCD69;
display: flex;
align-items: center;
justify-content: center;
- border-bottom: 1px solid black;
+ border-bottom: 2px solid black;
+ border-radius: 8px 8px 0 0;
.image-container {
position: relative;
- width: 40%;
- height: 100%;
+ width: 150px;
+ height: 150px;
border-radius: 50%;
- border: 1.5px solid black;
- transform: translateY(50%);
+ border: 2px solid black;
+ transform: translateY(37%);
+ background-color: #FFFCF3;
+
img {
position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- clip-path: circle(50%);
- border-radius: 50%;
+ top: 10%;
+ left: 10%;
+ width: 80%;
+ height: 80%;
+ }
+
+ .indeterminate {
+ position: absolute;
+ transform: translate(-50%, 50%);
}
}
}
&-content {
height: 45%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
+ position: relative;
+
+ .top {
+ position: absolute;
+ width: 100%;
+ top: 50%;
+
+ .hp {
+ margin-left: 10px;
+ opacity: 0.5;
+ }
+ }
+
+ .bottom {
+ position: absolute;
+ width: 100%;
+ top: 65%;
+
+ .exp {
+ transform: translateY(40%);
+ opacity: 0.5;
+ }
+ }
}
&-footer {
@@ -46,6 +80,23 @@
display: flex;
align-items: center;
justify-content: space-around;
- border-top: 1px solid black;
+ border-top: 2px solid black;
+
+ .stat {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ b {
+ font-size: 16px;
+ }
+
+ p {
+ font-size: 12px;
+ opacity: 0.5;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx
index 582dda1..94a9a12 100644
--- a/src/components/header/Header.tsx
+++ b/src/components/header/Header.tsx
@@ -3,7 +3,7 @@ import PokemonLogo from '@assets/images/pokemon-logo.png';
import PokemonPikachu from '@assets/images/pokemon-svg.png';
import './header.scss';
-const Header = (): JSX.Element => {
+const Header: React.FC = (): JSX.Element => {
return (
diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx
index 867fb43..2280db9 100644
--- a/src/components/home/Home.tsx
+++ b/src/components/home/Home.tsx
@@ -1,110 +1,19 @@
import React, { useState } from 'react';
-import {
- Button,
- Table,
- TableRow,
- TableHeaderCell,
- TableCell
-} from '@faculedesma/ledesma-lib';
-import PokemonCard from '@components/card/Card';
-import { usePokemons } from '@components/hooks/usePokemons';
-import { capitalizeFirstLetter } from '@utils/utils';
+import HomeTable from './HomeTable';
+import HomeCard from './HomeCard';
import './home.scss';
-const getPokemonIdFromURL = (url: string): string => {
- const parts = url.split('/');
- return parts[parts.length - 2];
-};
-
-const Home = (): JSX.Element => {
- const [offset, setOffset] = useState
(0);
- const [selectedPokemonId, setSelectedPokemonId] = useState<
- string | undefined
- >(undefined);
-
- const { data, isLoading, isError } = usePokemons(offset);
-
- if (isLoading) {
- return Loading...
;
- }
-
- if (isError) {
- return Error!
;
- }
-
- const handleSelectRow = (id: string): void => setSelectedPokemonId(id);
-
- const handlePrev = (): void => setOffset(offset - 10);
-
- const handleNext = (): void => setOffset(offset + 10);
-
- if (data !== undefined) {
- const columns = Object.keys(data.results[0]).map((column) =>
- capitalizeFirstLetter(column)
- );
- const rows = data.results.map((row: any) => [
- capitalizeFirstLetter(row.name),
- row.url
- ]);
+const Home: React.FC = (): JSX.Element => {
+ const [selectedPokemonId, setSelectedPokemonId] = useState('');
- return (
-
-
- <>
-
-
- <>
- {columns?.map((name, columnIndex) => {
- return (
-
- {name}
-
- );
- })}
- >
-
-
-
- {rows?.map((row: any, rowIndex: number) => {
- return (
- handleSelectRow(getPokemonIdFromURL(row[1]))}
- >
- <>
- {row.map((data: any, rowDataIndex: number) => {
- return {data};
- })}
- >
-
- );
- })}
-
- >
-
-
-
-
- Page: {offset / 10 + 1}
- Pages: {Math.trunc(data.count / 10) + 1}
-
- {selectedPokemonId != null ? (
-
- ) : null}
-
- );
- }
+ const handleSelectPokemonId = (id: string): void => setSelectedPokemonId(id);
- return <>>;
+ return (
+
+
+
+
+ );
};
export default Home;
diff --git a/src/components/home/HomeCard.tsx b/src/components/home/HomeCard.tsx
new file mode 100644
index 0000000..c2c80ff
--- /dev/null
+++ b/src/components/home/HomeCard.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import Card from '@components/card/Card';
+import { useSinglePokemon } from '@components/hooks/useSinglePokemon';
+
+interface IPokemonCardProps {
+ pokemonId: string;
+}
+
+const EmptyCard: React.FC = (): JSX.Element => (
+ No pokemon selected
+);
+
+const LoadingCard: React.FC = (): JSX.Element => (
+
+);
+
+const HomeCard: React.FC = ({ pokemonId }): JSX.Element => {
+ const { data, isLoading, isError } = useSinglePokemon(pokemonId);
+
+ if (pokemonId === '') {
+ return ;
+ }
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (isError) {
+ return Error!
;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default HomeCard;
diff --git a/src/components/home/HomeTable.tsx b/src/components/home/HomeTable.tsx
new file mode 100644
index 0000000..8819ba8
--- /dev/null
+++ b/src/components/home/HomeTable.tsx
@@ -0,0 +1,99 @@
+import React, { useState } from 'react';
+import {
+ Table,
+ TableRow,
+ TableHeaderCell,
+ TableCell,
+ TableSkeleton,
+ Pagination
+} from '@faculedesma/ledesma-lib';
+import { usePokemons } from '@components/hooks/usePokemons';
+import { capitalizeFirstLetter, getPokemonIdFromURL } from '@utils/utils';
+import './home.scss';
+
+interface IHomeTableProps {
+ setPokemonId: (id: string) => void;
+}
+
+const TableLoading: React.FC = (): JSX.Element => ;
+
+const HomeTable: React.FC = ({
+ setPokemonId
+}): JSX.Element => {
+ const [offset, setOffset] = useState(0);
+ let count: number = 0;
+ let rows: string[][] = [];
+ let columns: string[] = [];
+
+ const { data, isLoading, isError } = usePokemons(offset);
+
+ if (data !== undefined) {
+ columns = ['N#', capitalizeFirstLetter(Object.keys(data.results[0])[0])];
+ rows = data.results.map((row: any) => [
+ getPokemonIdFromURL(row.url),
+ capitalizeFirstLetter(row.name)
+ ]);
+ count = data.count;
+ }
+
+ if (isError) {
+ return Error!
;
+ }
+
+ const handleSelectRow = (id: string): void => setPokemonId(id);
+
+ const handlePrev = (): void => setOffset(offset - 10);
+
+ const handleNext = (): void => setOffset(offset + 10);
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+
+ <>
+
+
+ <>
+ {columns.map((name, columnIndex) => {
+ return (
+
+ {name}
+
+ );
+ })}
+ >
+
+
+
+ {rows.map((row, rowIndex) => {
+ return (
+ handleSelectRow(row[0])}
+ >
+ <>
+ {row.map((data, rowDataIndex) => {
+ return {data};
+ })}
+ >
+
+ );
+ })}
+
+ >
+
+ )}
+
+
+ );
+};
+
+export default HomeTable;
diff --git a/src/components/home/home.scss b/src/components/home/home.scss
index cc5500b..9888122 100644
--- a/src/components/home/home.scss
+++ b/src/components/home/home.scss
@@ -1,7 +1,57 @@
.home {
- width: 100%;
+ min-width: 100%;
+ min-height: 100%;
display: flex;
- flex-direction: column;
align-items: center;
- justify-content: center;
+ justify-content: space-evenly;
+ margin-top: 40px;
+
+ &-table {
+ width: 60%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+
+ .table {
+ background-color: #fffcf3;
+
+ thead {
+ th {
+ &:first-child {
+ width: 60px;
+ }
+ }
+ }
+
+ tbody {
+ tr {
+ cursor: pointer;
+
+ &:hover {
+ background-color: #fbd68b;
+ }
+
+ &:active {
+ background-color: #ffcd69;
+ }
+ }
+ }
+ }
+
+ .table-skeleton {
+ height: 528px;
+ background-color: #fffcf3;
+ }
+
+ .pagination {
+ margin-top: 20px;
+ }
+ }
+
+ &-card {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
}
diff --git a/src/components/hooks/useSinglePokemon.ts b/src/components/hooks/useSinglePokemon.ts
index bcb4827..039d458 100644
--- a/src/components/hooks/useSinglePokemon.ts
+++ b/src/components/hooks/useSinglePokemon.ts
@@ -36,7 +36,10 @@ export const useSinglePokemon = (
): IUseSinglePokemonResponse => {
const { data, isError, isLoading } = useQuery(
['pokemon', pokemonId],
- getPokemon
+ getPokemon,
+ {
+ enabled: pokemonId !== ''
+ }
);
return { data, isError, isLoading };
};
diff --git a/src/index.scss b/src/index.scss
index 38d44a1..a11c1cf 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,21 +1,12 @@
-:root {
+body {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
-
- color-scheme: light dark;
- // background-color: rgba(255, 255, 255, 0.87);
- color: #242424;
-}
-
-body {
+ width: 100%;
margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
min-height: 100vh;
- background-color: white;
+ color: #242424;
}
h1 {
diff --git a/src/main.tsx b/src/main.tsx
index e2d76f9..1af1b5c 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,13 +1,13 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import App from "@components/app/App";
-import { QueryClient, QueryClientProvider } from "react-query";
-import { ReactQueryDevtools } from "react-query/devtools";
-import "./index.scss";
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from '@components/app/App';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { ReactQueryDevtools } from 'react-query/devtools';
+import './index.scss';
const queryClient = new QueryClient();
-ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
diff --git a/src/utils/_variables.scss b/src/utils/_variables.scss
new file mode 100644
index 0000000..10ee0ae
--- /dev/null
+++ b/src/utils/_variables.scss
@@ -0,0 +1,2 @@
+// colors
+$primary-colors: #fdf8e3;
\ No newline at end of file
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index a7af4a4..b2f32ec 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1,2 +1,7 @@
export const capitalizeFirstLetter = (word: string): string =>
word.charAt(0).toUpperCase() + word.slice(1);
+
+export const getPokemonIdFromURL = (url: string): string => {
+ const parts = url.split('/');
+ return parts[parts.length - 2];
+};
diff --git a/test/Home.spec.tsx b/test/HomeTable.spec.tsx
similarity index 78%
rename from test/Home.spec.tsx
rename to test/HomeTable.spec.tsx
index 57cfc54..147f08e 100644
--- a/test/Home.spec.tsx
+++ b/test/HomeTable.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import Home from '../src/components/home/Home';
+import HomeTable from '../src/components/home/HomeTable';
import { usePokemons } from '../src/components/hooks/usePokemons';
import * as hooks from '../src/components/hooks/usePokemons';
import { render } from '@testing-library/react';
@@ -23,26 +23,30 @@ const mockedData = {
]
};
-describe('', () => {
+const homeTableProps = {
+ setPokemonId: jest.fn()
+};
+
+describe('', () => {
it('fetches all pokemons', async () => {
jest.spyOn(hooks, 'usePokemons').mockImplementation(() => ({
data: mockedData,
isError: false,
isLoading: false
}));
- await render();
+ await render();
expect(usePokemons).toHaveBeenCalledTimes(1);
expect(usePokemons).toHaveBeenCalledWith(0);
});
- it('shows loading spinner', async () => {
+ it('shows table skeleton on loading', async () => {
jest.spyOn(hooks, 'usePokemons').mockImplementation((offset) => ({
data: undefined,
isError: false,
isLoading: true
}));
- const { getByText } = await render();
- expect(getByText('Loading...')).toBeInTheDocument();
+ const { container } = await render();
+ expect(container.firstChild?.firstChild).toHaveClass('table-skeleton');
});
it('shows error', async () => {
@@ -51,7 +55,7 @@ describe('', () => {
isError: true,
isLoading: false
}));
- const { getByText } = await render();
+ const { getByText } = await render();
expect(getByText('Error!')).toBeInTheDocument();
});
});