From 09ec89f8f8d7760bf74e030cae49c639d06488df Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Wed, 20 Dec 2023 18:07:40 +0800 Subject: [PATCH] Add form --- src/components/Form/FormError.tsx | 8 + src/components/Form/index.ts | 2 + src/components/Pokemon/PokemonForm.tsx | 339 ++++++++++++++++++ src/components/Pokemon/PokemonList.tsx | 49 +-- src/components/Pokemon/PokemonTypeLabel.tsx | 44 +++ src/components/Pokemon/index.ts | 2 +- src/layout/AdminLayout/AdminLayout.tsx | 2 +- src/models/pokemon.ts | 49 ++- src/pages/_app.tsx | 4 + src/pages/pokemons/[id]/edit.tsx | 64 ++++ .../pokemons/{client.tsx => client/index.tsx} | 0 src/pages/pokemons/create.tsx | 17 + 12 files changed, 533 insertions(+), 47 deletions(-) create mode 100644 src/components/Form/FormError.tsx create mode 100644 src/components/Form/index.ts create mode 100644 src/components/Pokemon/PokemonForm.tsx create mode 100644 src/components/Pokemon/PokemonTypeLabel.tsx create mode 100644 src/pages/pokemons/[id]/edit.tsx rename src/pages/pokemons/{client.tsx => client/index.tsx} (100%) create mode 100644 src/pages/pokemons/create.tsx diff --git a/src/components/Form/FormError.tsx b/src/components/Form/FormError.tsx new file mode 100644 index 0000000..f4607ad --- /dev/null +++ b/src/components/Form/FormError.tsx @@ -0,0 +1,8 @@ +import { Form } from 'react-bootstrap' +import React from 'react' + +export default function FormError(props: { message?: string }) { + const { message } = props + + return message && {message} +} diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts new file mode 100644 index 0000000..24a3a69 --- /dev/null +++ b/src/components/Form/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as FormError } from './FormError' diff --git a/src/components/Pokemon/PokemonForm.tsx b/src/components/Pokemon/PokemonForm.tsx new file mode 100644 index 0000000..dce5d07 --- /dev/null +++ b/src/components/Pokemon/PokemonForm.tsx @@ -0,0 +1,339 @@ +import { + Pokemon, + PokemonEggGroup, + pokemonEggGroups, + PokemonType, + pokemonTypes, +} from '@models/pokemon' +import { PokemonTypeLabel } from '@components/Pokemon/index' +import { + Alert, Button, Col, Form, Row, +} from 'react-bootstrap' +import { Controller, SubmitHandler, useForm } from 'react-hook-form' +import React, { useState } from 'react' +import classNames from 'classnames' +import Image from 'next/image' +import { FormError } from '@components/Form' + +type Inputs = { + name: string; + types: PokemonType[]; + eggGroups: PokemonEggGroup[]; + hp: number | null; + attack: number | null; + defense: number | null; + special_attack: number | null; + special_defense: number | null; + speed: number | null; +} + +type Props = { + pokemon?: Pokemon; +} + +export default function PokemonForm(props: Props) { + const { pokemon } = props + + const defaultValues = (): Inputs => { + if (pokemon) { + return { + name: pokemon.name, + types: pokemon.types, + eggGroups: pokemon.egg_groups, + hp: pokemon.hp, + attack: pokemon.attack, + defense: pokemon.defense, + special_attack: pokemon.special_attack, + special_defense: pokemon.special_defense, + speed: pokemon.speed, + } + } + + return { + name: '', + types: [], + eggGroups: [], + hp: null, + attack: null, + defense: null, + special_attack: null, + special_defense: null, + speed: null, + } + } + + const { + register, + control, + handleSubmit, + setValue, + formState: { errors }, + reset, + } = useForm({ + defaultValues: defaultValues(), + }) + + const [submitting, setSubmitting] = useState(false) + const [showNotification, setShowNotification] = useState(false) + const [notificationMessage, setNotificationMessage] = useState('') + + const onSubmit: SubmitHandler = async (data) => { + setSubmitting(true) + + // Change to your real submit here + const fakeSubmit = () => new Promise((resolve) => { + setTimeout(() => { + resolve(data) + }, 1500) + }) + + const res = await fakeSubmit() + + setSubmitting(false) + window.scrollTo(0, 0) + setShowNotification(true) + + if (res) { + setNotificationMessage('Record saved successfully.') + return + } + + setNotificationMessage('Unexpected error occurred, please try again.') + } + + return ( +
+ {showNotification && ( + setShowNotification(false)} dismissible> + {notificationMessage} + + )} + + {pokemon && ( +
+ {pokemon.pokemondb_identifier} +
+ )} + + + Name + + + + + + Types +
+ + {pokemonTypes.map((type) => ( + + + + + + + + + + + ))} + +
+ +
+ + + Egg groups +
+ + {pokemonEggGroups.map((eggGroup) => ( + + + + ))} + +
+ +
+ + + Hp + + + + + + Attack + + + + + + Defense + + + + + + Special attack + + + + + + Special defense + + + + + + Speed + ( + { + if (e.target.value === '') { + setValue('speed', null) + return + } + setValue('speed', Number(e.target.value)) + }} + /> + )} + /> + + + + + +
+ ) +} diff --git a/src/components/Pokemon/PokemonList.tsx b/src/components/Pokemon/PokemonList.tsx index c0da04d..d077d71 100644 --- a/src/components/Pokemon/PokemonList.tsx +++ b/src/components/Pokemon/PokemonList.tsx @@ -5,47 +5,8 @@ import React from 'react' import Image from 'next/image' import { Pokemon } from '@models/pokemon' import { THSort } from '@components/TableSort' - -const typeColorMap: Record = { - normal: '#aa9', - fighting: '#b54', - flying: '#89f', - poison: '#a59', - ground: '#db5', - rock: '#ba6', - bug: '#ab2', - ghost: '#66b', - steel: '#aab', - fire: '#f42', - water: '#39f', - grass: '#7c5', - electric: '#fc3', - psychic: '#f59', - ice: '#6cf', - dragon: '#76e', - dark: '#754', - fairy: '#e9e', - unknown: '#aa9', - shadow: '#aa9', -} - -type TypeLabelProps = { - type: string; -} - -const TypeLabel = ({ type }: TypeLabelProps) => ( - - {type} - -) +import PokemonTypeLabel from '@components/Pokemon/PokemonTypeLabel' +import Link from 'next/link' type Props = { pokemons: Pokemon[]; @@ -90,7 +51,7 @@ export default function PokemonList(props: Props) { {pokemon.name} - {pokemon.types.map((type) => )} + {pokemon.types.map((type) => )} {pokemon.egg_groups.join('\n')} {pokemon.hp} @@ -113,7 +74,9 @@ export default function PokemonList(props: Props) { Info - Edit + + Edit + = { + normal: '#aa9', + fighting: '#b54', + flying: '#89f', + poison: '#a59', + ground: '#db5', + rock: '#ba6', + bug: '#ab2', + ghost: '#66b', + steel: '#aab', + fire: '#f42', + water: '#39f', + grass: '#7c5', + electric: '#fc3', + psychic: '#f59', + ice: '#6cf', + dragon: '#76e', + dark: '#754', + fairy: '#e9e', + unknown: '#aa9', + shadow: '#aa9', +} + +type Props = { + type: string; +} + +export default function PokemonTypeLabel({ type }: Props) { + return ( + + {type} + + ) +} diff --git a/src/components/Pokemon/index.ts b/src/components/Pokemon/index.ts index 7f99819..5189a41 100644 --- a/src/components/Pokemon/index.ts +++ b/src/components/Pokemon/index.ts @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export { default as PokemonList } from './PokemonList' +export { default as PokemonTypeLabel } from './PokemonTypeLabel' diff --git a/src/layout/AdminLayout/AdminLayout.tsx b/src/layout/AdminLayout/AdminLayout.tsx index 858cfa2..95d522a 100644 --- a/src/layout/AdminLayout/AdminLayout.tsx +++ b/src/layout/AdminLayout/AdminLayout.tsx @@ -58,7 +58,7 @@ export default function AdminLayout({ children }: PropsWithChildren) {
- + {children}
diff --git a/src/models/pokemon.ts b/src/models/pokemon.ts index de6b0ea..1d56fd1 100644 --- a/src/models/pokemon.ts +++ b/src/models/pokemon.ts @@ -3,8 +3,8 @@ export interface Pokemon { identifier: string; pokemondb_identifier: string; name: string; - types: string[]; - egg_groups: string[]; + types: PokemonType[]; + egg_groups: PokemonEggGroup[]; hp: number; attack: number; defense: number; @@ -13,3 +13,48 @@ export interface Pokemon { speed: number; total: number; } + +export const pokemonTypes = [ + 'normal', + 'fighting', + 'flying', + 'poison', + 'ground', + 'rock', + 'bug', + 'ghost', + 'steel', + 'fire', + 'water', + 'grass', + 'electric', + 'psychic', + 'ice', + 'dragon', + 'dark', + 'fairy', + 'unknown', + 'shadow', +] as const + +export type PokemonType = typeof pokemonTypes[number] + +export const pokemonEggGroups = [ + 'Monster', + 'Water 1', + 'Bug', + 'Flying', + 'Field', + 'Fairy', + 'Grass', + 'Human-Like', + 'Water 3', + 'Mineral', + 'Amorphous', + 'Water 2', + 'Ditto', + 'Dragon', + 'Undiscovered', +] as const + +export type PokemonEggGroup = typeof pokemonEggGroups[number] diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a621748..20380e2 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -6,6 +6,8 @@ import { config } from '@fortawesome/fontawesome-svg-core' import '@fortawesome/fontawesome-svg-core/styles.css' import { SSRProvider } from 'react-bootstrap' import { ProgressBar } from '@components/ProgressBar' +import axios from 'axios' +import axiosRetry from 'axios-retry' // You change this configuration value to false so that the Font Awesome core SVG library // will not try and insert