Skip to content

Commit

Permalink
Frontend fixes, Author form
Browse files Browse the repository at this point in the history
* fix: use layout as a parent element

* feat: define a height limit for tables

* feat(frontend): create author form
  • Loading branch information
gabrielborgesdm authored May 25, 2024
1 parent 3854115 commit 8959a44
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 92 deletions.
65 changes: 64 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@hookform/resolvers": "^3.4.2",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand All @@ -13,10 +14,12 @@
"axios": "^1.7.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"yup": "^1.4.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down
84 changes: 84 additions & 0 deletions frontend/src/components/AuthorForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from "react";
import { AuthorCreate } from "types/author";
import Input from "./Form/Input";
import { SubmitHandler, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"
import AuthorSchema from "./Schemas/AuthorSchema";
import Form, { FormMessage } from "./Form/Form";
import ManagementService from "services/managementService";

const managementService = new ManagementService()


const AuthorForm: React.FC = () => {
const messageInitialState = { value: "", isError: false }
const [isLoading, setIsLoading] = useState(false)
const [message, setMessage] = useState<FormMessage>(messageInitialState)
const {
register,
handleSubmit,
reset,

formState: { errors },
} = useForm({ resolver: yupResolver(AuthorSchema) })



const onSubmit: SubmitHandler<AuthorCreate> = async (data: AuthorCreate) => {
setIsLoading(true)
const author = await managementService.createAuthor(data)
console.log(author, author === undefined)
setMessage({
value: author ? "Author created with success" : "It wansn't possible to create the author, try again",
isError: author === undefined
})
setIsLoading(false)
// onReset()
}

const onReset = () => {
setMessage(messageInitialState)
reset(undefined)
}

return (
<Form
onSubmit={handleSubmit(onSubmit)}
onReset={onReset}
title="Author Creation"
description="Enter the author details"
message={message}
isLoading={isLoading}>
<>
<Input
label="Author name"
name="name"
register={register}
errorMessage={errors.name?.message}
/>

<Input
label="Email address"
name="email"
register={register}
errorMessage={errors.email?.message}
/>

<Input
label="Nationality"
name="nationality"
register={register}
errorMessage={errors.nationality?.message}
/>

<Input
label="Birth date"
name="birthDate"
register={register}
errorMessage={errors.birthDate?.message}
/></>
</Form>
)
}

export default AuthorForm
48 changes: 48 additions & 0 deletions frontend/src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { ReactNode } from "react";
import FormControlButtons from "./FormSubmitButtons";

export interface FormMessage {
value: string;
isError: boolean;
}
interface FormProps {
title: string;
description: string;
isLoading: boolean;
onSubmit: () => Promise<void>;
onReset: () => void;
message: FormMessage;
children: ReactNode;
}

const Form: React.FC<FormProps> = ({ title, description, onSubmit, onReset, message, isLoading, children }) => {
return (
<form onSubmit={onSubmit}>
{message.value &&
<div
className={"border px-4 py-3 my-3 rounded relative " +
(message.isError ? "bg-red-100 border-red-400 text-red-700" : "bg-teal-100 border-teal-500 text-teal-900")
}
role="alert"
>
<strong className="font-bold">{message.isError ? "Ops!" : "Yes!"} </strong>
<span className="block sm:inline">{message.value}</span>
</div>
}
<div className="space-y-12">
<div className="border-b border-gray-900/10 pb-12">
<h2 className="text-base font-semibold leading-7 text-gray-900">{title}</h2>
<p className="mt-1 text-sm leading-6 text-gray-600">{description}</p>
<hr />
<div className="mt-5 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
{children}
</div>
</div>
</div>

<FormControlButtons isLoading={isLoading} reset={onReset} />
</form >
)
}

export default Form
34 changes: 34 additions & 0 deletions frontend/src/components/Form/FormSubmitButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

interface FormControlButtonsProps {
reset: () => void;
isLoading: boolean;
}
const FormControlButtons: React.FC<FormControlButtonsProps> = ({ isLoading, reset }) => {

return (
<div className="mt-6 flex items-center justify-end gap-x-6">
<button
type="button"
disabled={isLoading}
className={`text-sm font-semibold leading-6 ${isLoading ? "text-gray-200" : "text-gray-900"}`}
onClick={reset}
>
Reset
</button>
<button
type="submit"
disabled={isLoading}
className={`bg-indigo-600 rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm` +
(
isLoading ? " bg-gray-200" : " hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
)
}
>
{isLoading ? "Loading" : "Save"}
</button>
</div >
)
}


export default FormControlButtons
31 changes: 31 additions & 0 deletions frontend/src/components/Form/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { InputHTMLAttributes } from "react";
import { UseFormRegister } from "react-hook-form";

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
name: string;
register: UseFormRegister<any>;
errorMessage?: string
}

const Input: React.FC<InputProps> = ({ label, name, register, errorMessage, ...otherOptions }) => {

return (
<div className="sm:col-span-3">
<label htmlFor={name} className="block text-sm font-medium leading-6 text-gray-900">
{label}
</label>
<div className="mt-2">
<input
{...otherOptions}
{...register(name)}
id={name}
className="block w-full rounded-md border-0 py-1.5 px-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
<p className="text-red-500 text-xs italic">{errorMessage}</p>
</div>
</div>
)
}

export default Input
16 changes: 8 additions & 8 deletions frontend/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Navbar } from "./Navbar/Navbar";
import { Outlet } from "react-router-dom";
import { Navbar } from "./Navbar";

interface LayoutProps {
element: React.ReactNode;
}

const Layout: React.FC<LayoutProps> = ({ element }) => (
const Layout: React.FC = () => (
<>
<Navbar />
{element}
<div className="sm:rounded-lg w-1/2 mx-auto mt-4">
<Outlet />
</div>
</>
);

export default Layout;
export default Layout

File renamed without changes.
13 changes: 13 additions & 0 deletions frontend/src/components/Schemas/AuthorSchema.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as yup from "yup"

const AuthorSchema = yup
.object({
name: yup.string().required().min(1).max(300),
email: yup.string().email().min(1).max(320),
nationality: yup.string().min(1).max(100),
birthDate: yup.string().matches(/^(?:(?:19|20)\d{2})-(?:(?:0[1-9]|1[0-2]))-(?:(?:0[1-9]|[12]\d|3[01]))$/, { message: "Follow the format AAAA-MM-DD" }),
})
.required()


export default AuthorSchema
Loading

0 comments on commit 8959a44

Please sign in to comment.