move to git.n39.euall books in the bag ...

This commit is contained in:
0ry5 2024-09-09 20:41:15 +02:00
commit 8cc2662092
64 changed files with 134252 additions and 0 deletions
frontend/src/shared/components/modals

View file

@ -0,0 +1,268 @@
import React, { useCallback, useEffect, useState } from "react";
import { Button, Col, Form, Modal, Row } from "react-bootstrap";
import { ImBook, ImCamera } from "react-icons/im";
import { ModalHeader } from "./ModalHeader";
import { useForm, useWatch } from "react-hook-form";
import { Book, bookShelfs } from "../../../types/Book";
import { useScanner } from "../../utils/useScanner";
import { primary, primaryRGBA, secondary } from "../../../colors";
import { BookModalProps } from "./types";
type BookForm = Pick<Book, "isbn" | "title" | "shelf" | "published">;
export const BookModal = ({
open,
onClose,
book,
}: Omit<BookModalProps, "type">): React.JSX.Element => {
const isEdit = !!book;
const [showScanner, setShowScanner] = useState(false);
const { control, register, formState, setError, setValue, reset } =
useForm<BookForm>({
mode: "onChange",
defaultValues: book,
});
useEffect(() => {
reset(book);
}, [book, reset]);
const { scannerError, setScannerRef } = useScanner({
onDetected: (result) => {
setValue("isbn", result);
setShowScanner(false);
},
});
const values = useWatch({ control });
const [submitError, setSubmitError] = useState<string | undefined>();
const onSubmit = useCallback(
async (data: Partial<BookForm>) => {
setSubmitError(undefined);
let error = false;
if (!data.isbn?.length) {
setError("isbn", { message: "ISBN is required" });
error = true;
}
if (!data.title?.length) {
setError("title", { message: "Title is required" });
error = true;
}
if (!data.shelf?.length) {
setError("shelf", { message: "Shelf is required" });
error = true;
}
if (!data.published) {
setError("published", { message: "Year published is required" });
error = true;
}
if (error) return;
const createdBook = await fetch(
isEdit ? "api/books/edit" : "/api/books/create",
{
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
}
);
if (!createdBook.ok) {
setSubmitError(await createdBook.text());
} else {
onClose();
}
},
[isEdit, onClose, setError]
);
return (
<Modal
show={open}
onHide={onClose}
style={{ backgroundColor: primaryRGBA }}
centered
>
<ModalHeader
onClose={onClose}
title={`${!!book ? "Edit" : "Add new"} Book`}
icon={<ImBook size={50} className="ml-0 mr-auto" />}
/>
<Modal.Body
style={{
border: "2px solid black",
borderTop: "none",
backgroundColor: primary,
}}
>
{!showScanner && (
<Form
className="mb-2"
onSubmit={(ev) => {
ev.preventDefault();
onSubmit(values);
}}
>
<Form.Group as={Row} className="mb-2">
<Col sm="2">
<Form.Label>ISBN</Form.Label>
</Col>
<Col className="d-flex flex-column">
<div className="d-flex">
<Form.Control
{...register("isbn", { required: true, maxLength: 360 })}
isInvalid={!!formState.errors.isbn}
style={{
borderRadius: "5px 0px 0px 5px",
}}
/>
<Button
style={{
borderRadius: "0px 5px 5px 0px",
backgroundColor: secondary,
color: "black",
border: "2px solid black",
}}
className="mr-2 pt-0"
disabled={showScanner}
onClick={() => setShowScanner(true)}
>
<ImCamera size={20} />
</Button>
</div>
<Form.Control.Feedback
style={{ display: !formState.errors.isbn ? "none" : "block" }}
type="invalid"
>
{!formState.errors.isbn
? "ISBN is required"
: formState.errors.isbn?.message}
</Form.Control.Feedback>
</Col>
</Form.Group>
<Form.Group as={Row} className="mb-2">
<Col sm="2">
<Form.Label>Title</Form.Label>
</Col>
<Col>
<Form.Control
{...register("title", { required: true, maxLength: 360 })}
isInvalid={!!formState.errors.title}
/>
<Form.Control.Feedback type="invalid">
{!values.title
? "Title is required"
: formState.errors.title?.message}
</Form.Control.Feedback>
</Col>
</Form.Group>
<Form.Group as={Row} className="mb-2">
<Col sm="2">
<Form.Label>Year published</Form.Label>
</Col>
<Col>
<Form.Control
{...register("published", {
required: true,
maxLength: 360,
validate: {
isReasonable: (value) => {
if (value > 1900 && value < 2150) {
return true;
}
return `${value} is not a reasonable year`;
},
isNumber: (value) => {
if (value && value.toString().match(/^[0-9\b]+$/)) {
return true;
}
return "Please enter a valid year.";
},
},
})}
isInvalid={!!formState.errors.published}
/>
<Form.Control.Feedback type="invalid">
{!values.published
? "Year published is required"
: formState.errors.published?.message}
</Form.Control.Feedback>
</Col>
</Form.Group>
<Form.Group as={Row} className="mb-2">
<Col sm="2">
<Form.Label>Shelf</Form.Label>
</Col>
<Col>
<Form.Select
{...register("shelf", { required: true })}
isInvalid={!!formState.errors.shelf}
>
{Object.keys(bookShelfs).map((key) => (
<option
key="key"
value={bookShelfs[key as keyof typeof bookShelfs]}
>
{key}
</option>
))}
</Form.Select>
<Form.Control.Feedback type="invalid">
{!values.shelf
? "Shelf is required"
: formState.errors.shelf?.message}
</Form.Control.Feedback>
</Col>
</Form.Group>
<div className="d-flex mx-auto mb-auto mt-2 w-100">
<Button
style={{
borderRadius: "5px",
backgroundColor: secondary,
color: "black",
border: "2px solid black",
marginLeft: "auto",
marginRight: "10px",
}}
onClick={() => onSubmit(values)}
>
{isEdit ? "Submit" : "Add Book"}
</Button>
</div>
</Form>
)}
{showScanner && (
<div
className="w-100 overflow-hidden"
ref={(ref) => setScannerRef(ref)}
style={{ position: "relative", height: "25vh" }}
>
<canvas
className="drawingBuffer w-100 position-absolute"
style={{
height: "100%",
border: "2px solid black",
}}
/>
</div>
)}
{scannerError ? (
<p>
ERROR INITIALIZING CAMERA ${JSON.stringify(scannerError)} -- DO YOU
HAVE PERMISSION?
</p>
) : null}
{!!submitError && (
<Form.Control.Feedback style={{ display: "block" }} type="invalid">
{submitError}
</Form.Control.Feedback>
)}
</Modal.Body>
</Modal>
);
};