Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .storybook/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { CodeBlock, shadesOfPurple } from 'react-code-blocks';

export const Code = ({ children }) => (
export const Code = ({ children }: { children: string }) => (
<CodeBlock
text={children}
language="jsx"
Expand Down
1 change: 0 additions & 1 deletion .storybook/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const theme = create({
brandTitle: '⚛️ React Design Patterns',
brandUrl: 'https://example.com',
brandTarget: '_self',
//
colorPrimary: '#ffffff',
colorSecondary: '#A599E9',

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Label } from '@shared/components/Label/Label.component';
import { ErrorMessage } from '@shared/components/ErrorMessage/ErrorMessage.component';
import { HTMLAttributes } from 'react';

export interface ITextFieldProps {
export interface TextFieldProps {
hasError: boolean;
errorMessage?: string;
id: string;
Expand All @@ -19,7 +19,7 @@ export const TextFieldComponent = ({
id,
name,
label
}: ITextFieldProps) => (
}: TextFieldProps) => (
<div className="flex flex-col gap-2">
<Label htmlFor={id}>{label}</Label>
<Input id={id} name={name} hasError={hasError} {...input} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChangeEvent, useState } from 'react';
import { ITextFieldProps, TextFieldComponent } from '../components';
import { TextFieldProps, TextFieldComponent } from '../components';

/*
* Observations
Expand All @@ -16,7 +16,7 @@ interface IFieldProps {
id: string;
label: string;
errorMessage?: string;
children: (props: ITextFieldProps) => React.ReactNode;
children: (props: TextFieldProps) => React.ReactNode;
}

const validateTextString = (value: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export const Final = ({
</div>
<div className="px-6 pt-4 pb-2">
<a
href={cta.url}
href={cta.url.startsWith('http') ? cta.url : '#'}
rel="noopener noreferrer"
target={cta.url.startsWith('http') ? '_blank' : '_self'}
className={classnames(
'inline-block bg-gray-200 hover:bg-gray-300 transition-colors rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2',
classNames?.cta
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@ import classNames from 'classnames';
import { HTMLAttributes } from 'react';
import { IconOne, IconTwo } from '../icons';

interface IButton extends HTMLAttributes<HTMLButtonElement> {
interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
className?: string;
iconLeft?: React.ReactNode;
iconRight?: React.ReactNode;
children: React.ReactNode | React.ReactNode[];
}

const buttonClasses =
'middle none center rounded-lg bg-blue-500 py-3 px-6 font-sans text-xs font-bold uppercase text-white shadow-md shadow-blue-500/20 transition-all hover:shadow-lg hover:shadow-blue-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none inline-flex items-center justify-center';
const buttonClasses = [
'middle none center rounded-lg bg-blue-500 py-3 px-6',
'font-sans text-xs font-bold uppercase text-white',
'shadow-md shadow-blue-500/20 transition-all',
'hover:shadow-lg hover:shadow-blue-500/40',
'focus:opacity-[0.85] focus:shadow-none',
'active:opacity-[0.85] active:shadow-none',
'disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none',
'inline-flex items-center justify-center'
].join(' ');

export const Button = ({
className,
children,
iconLeft,
iconRight,
...rest
}: IButton) => {
}: ButtonProps) => {
return (
<button
{...rest}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ interface IForm {

export const Form = ({ onPokemonTypesUpdate }: IForm) => {
const [selectedPokemonTypes, setSelectedPokemonTypes] = useState<
string[]
>([]);
Set<string>
>(new Set());

const { data, isLoading } = usePokedex<TPokemonTypesApiResponse[]>({
path: 'types',
Expand All @@ -23,21 +23,19 @@ export const Form = ({ onPokemonTypesUpdate }: IForm) => {
const handleSubmit = (event: FormEvent) => {
event.preventDefault();

if (selectedPokemonTypes.length === 4) {
onPokemonTypesUpdate(selectedPokemonTypes);
if (selectedPokemonTypes.size === 4) {
onPokemonTypesUpdate(Array.from(selectedPokemonTypes));
}
};

const onPokemonTypeSelection = (type: string) => {
if (selectedPokemonTypes.includes(type)) {
setSelectedPokemonTypes(
selectedPokemonTypes.filter(
(pokemonType) => pokemonType !== type
)
);
const newTypes = new Set(selectedPokemonTypes);
if (newTypes.has(type)) {
newTypes.delete(type);
} else {
setSelectedPokemonTypes([...selectedPokemonTypes, type]);
newTypes.add(type);
}
setSelectedPokemonTypes(newTypes);
};

return (
Expand All @@ -48,28 +46,17 @@ export const Form = ({ onPokemonTypesUpdate }: IForm) => {
<form onSubmit={handleSubmit} noValidate>
{isLoading && (
<div className="grid grid-cols-2 md:grid-cols-6 gap-3">
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
<Skeleton height="h-[48px]" />
{Array.from({ length: 12 }, (_, i) => (
<Skeleton key={i} height="h-[48px]" />
))}
</div>
)}
<div className="grid grid-cols-2 md:grid-cols-6 gap-3">
{data &&
data.length &&
data.map((pokemonType) => {
const isSelected =
selectedPokemonTypes.includes(pokemonType);
const hasSelectedAllOptions =
selectedPokemonTypes.length === 4;
const isSelected = selectedPokemonTypes.has(pokemonType);
const hasSelectedAllOptions = selectedPokemonTypes.size === 4;

return (
<label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ export const Screen = () => {
// 🪜 State Lifting: Setting up a function which will update the state instead of bleeding this complexity into the UI component.
const onPokemonTypesUpdate = (types: string[]) => {
const newlyUpdatedPokemon = { ...selectedPokemon };
const typesSet = new Set(types);

selectedPokemonTypes
.filter((type) => {
return !types.find((selectedType) => selectedType === type);
})
.forEach((type) => {
// Remove types that are no longer selected
selectedPokemonTypes.forEach((type) => {
if (!typesSet.has(type)) {
delete newlyUpdatedPokemon[type];
});
}
});

setSelectedPokemon(newlyUpdatedPokemon);

setSelectedPokemonTypes(types);
};

Expand All @@ -41,7 +40,7 @@ export const Screen = () => {
<main className="h-screen p-6">
<img
src="/pokemon-battleground.webp"
alt="hello"
alt="Pokemon battleground background"
className="fixed will-change-scroll left-0 right-0 top-0 bottom-0 z-0 h-full w-full object-cover"
/>
<div className="fixed will-change-scroll left-0 right-0 top-0 bottom-0 z-10 h-full w-full bg-black opacity-25" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const ChevronDown = () => (
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<rect
id="Rectangle"
fill-rule="nonzero"
fillRule="nonzero"
x="0"
y="0"
width="24"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Final = () => (
id="accordion-one"
title="Accordion Button One"
onClick={() => {
console.log('TRACK');
// TODO: Replace with proper analytics tracking
}}
>
<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface IHeading extends HTMLAttributes<HTMLHeadingElement> {
as?: AllowedHTMLElements;
size?: FontSizes;
sizeMd?: FontSizes;
children?: React.ReactNode | React.ReactNode[];
children?: React.ReactNode;
}

const Heading = ({
Expand All @@ -36,17 +36,17 @@ const Heading = ({
);

const largeFontSize = useMemo(
() =>
[
'md:text-sm',
'md:text-md',
'md:text-lg',
'md:text-xl',
'md:text-2xl',
'md:text-3xl'
].find((fontSizeClassName) =>
sizeMd ? fontSizeClassName.includes(sizeMd) : false
),
() => {
const sizeMap: Record<FontSizes, string> = {
sm: 'md:text-sm',
md: 'md:text-md',
lg: 'md:text-lg',
xl: 'md:text-xl',
'2xl': 'md:text-2xl',
'3xl': 'md:text-3xl'
};
return sizeMd ? sizeMap[sizeMd] : undefined;
},
[sizeMd]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export const Modal = ({
event.preventDefault();
};

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
onClose();
}
};

if (!document.body) {
return null;
}

return createPortal(
<div
className={classNames(
Expand All @@ -40,6 +50,7 @@ export const Modal = ({
)}
role="button"
onClick={onClose}
onKeyDown={handleKeyDown}
tabIndex={0}
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ type Story = StoryObj<typeof Final>;
* to learn more about using the canvasElement to query the DOM
*/
export const Default: Story = {
play: async () => {},
play: () => {},
args: {}
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const fetchPokemons = async (dispatch: Dispatch<IActionType>) => {

try {
await pokemonManager.fetchPokemons(12);
const pokemoneState = pokemonManager.getState();
const pokemonState = pokemonManager.getState();
dispatch({
type: ActionNames.RECEIVED_POKEMONS,
payload: pokemoneState.pokemons
payload: pokemonState.pokemons
});
} catch (e) {
dispatch({ type: ActionNames.ERROR_POKEMONS });
Expand Down Expand Up @@ -115,8 +115,7 @@ export const Final = () => {
<section>
<h2 className="text-2xl font-bold mb-4">Pokemon Cards</h2>
<div className="grid grid-cols-6 gap-6">
{pokemons &&
pokemons.length > 0 &&
{pokemons && pokemons.length > 0 &&
pokemons.map((pokemon) => (
<div key={pokemon.id}>
<img
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const Component = ({

export const Final = withPokemons<
IMapStateToPropsComponentOneResponse,
IActionsComponentOneResponse
IActionsComponentOneResponse,
{ title: string }
>(
mapStateToProps,
mapActionsToProps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ export interface IPokemonManagerActions {
fetchPokemons: (total: number) => Promise<void>;
}

export const withPokemons = <TMapperResponse, TActionResponse>(
export const withPokemons = <
TMapperResponse,
TActionResponse,
TProps
>(
mapper: (state: IPokemonManagerState) => TMapperResponse,
actions: (actions: IPokemonManagerActions) => TActionResponse
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (Component: React.FC<any>) => (props: any) => {
return (Component: any) => (props: TProps) => {
const pokemonManager = useMemo(() => new PokemonManager(), []);
const [pokemonManagerState, setPokemonManagerState] = useState(
new PokemonManager().getState()
pokemonManager.getState()
);

const pokemonManager = useMemo(() => new PokemonManager(), []);

return (
<Component
{...props}
Expand Down
8 changes: 6 additions & 2 deletions src/shared/hooks/usePokedex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,17 @@ export const usePokedex = <TResponse>({
const response = await fetch(
`https://api.pokemontcg.io/v2/${path}?${queryParams}`
);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const json = await response.json();

dispatch({ type: 'SUCCESS', payload: json.data });
} catch (e) {
dispatch({ type: 'ERROR' });

console.error('Error');
console.error('Failed to fetch Pokemon data:', e instanceof Error ? e.message : 'Unknown error');
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/shared/modules/PokemonManager/PokemonManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export class PokemonManager {
})
);

console.log('Pokémon fetched successfully:', this.pokemons);
console.log('Pokémon fetched successfully:', this.pokemons.length, 'items');
} catch (error) {
console.error(error);
console.error('Failed to fetch Pokemon:', error instanceof Error ? error.message : 'Unknown error');
}
}

Expand Down