Version 1.0
--------------------------------------------------------------------------------------------------------------------------------
Ir no spring initializer link aqui para acelerar processo de desenvolvimento configurando as dependências.
--------------------------------------------------------------------------------------------------------------------------------
clicar nos botões com estes nomes para instalar as dependências dentro da IDE : maven, seu-projeto, lifecycle, package
---------------------------------------------------------------------------------------------------------------------------------
com file main selecionado vai nas configurações e clica : build execution deployment, compiler, ativar []build project automaticaly
mesmo menu , vai em: advanced settings, clicar no check compiler []Allow auto-make to start even if developed aplication is currently running
---------------------------------------------------------------------------------------------------------------------------------
responsável por concentrar os requests para pegar os dados do cardápio e armazenar mais info no banco de dados
fazer notação spring da classe ser o controler @RestController bem emcima da linha que tem assinatura do método
---------------------------------------------------------------------------------------------------------------------------------
cria um método dentro da classe para retornar para o front-end todos cardápios do banco de dados public tipo getAll(){}
---------------------------------------------------------------------------------------------------------------------------------
vai no spring initializer link aqui e baixa mais dependências
clica em explore , dentro do pom.xml online seleciona as tags que tem JPA e POSTEGRESQL copiar para colar dentro das dependências do projeto no intellij
---------------------------------------------------------------------------------------------------------------------------------
Precisa: ligar o pgAdmin, entrar com senha bd, conectar usuario e senha do banco do IntelliJ, executar backend no intelliJ , aí vejo se no navegador aparece igual a kipper dev
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------
crio um objeto do tipo da classe Entity "Food" para apartir dele criar CLasse "Food" em pacote próprio food
em cima da assinatura da classe Food{} uso mapeamento do jpa para tabela do BD a notação @Table(name="foods") e o nome da entity @Entity(name="foods")
faça estas duas notações para definir chave primária , e id gerado automaticamente @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
---------------------------------------------------------------------------------------------------------------------------------
tem que ser interface dentro do pacote food que extenda o Objeto JpaRepository<Tipo-Objeto, Tipo-dado-id>
public interface FoodRepository extends JpaRepository<Food, Long> {
}
herda do objeto todos os metodos para manipular dados, usando estes metodos dentro do metodos getAll() que fica dentro da classe FoodController{}
indicar para o Spring que ele faça a injeção dessa dependência dentro da classe FoodController{} "que ele se responsabilize para instanciar tal classe quando eu for usar objeto dela"
notação para injeção de dependência @Autowired bem em cima quando declarei objeto da classe Food{} dentro da minha classe FoodController{}
cria uma lista do tipo de objeto da classe Food que tem o repository executando seus metodos para trazer tudo do banco de dados List<Food> foodList = repository.findAll();
---------------------------------------------------------------------------------------------------------------------------------
Agora vai no insomnia como usar adiciona uma colection , escolhe o método GET pela url http://localhost:8080/food e clica botão [send]
Não é uma boa pratica retornar do metodo o tipo de dado diretamente a Entity food que fizemos em getAll()
isso vai ser um record "dado estático" que vai receber por parametro os dados igual atributos do objeto Food
Dentro do record FoodResponseDTO{} eu vou receber pelo parametro do contrutor dele um objeto do tipo Food , e por dentro do contrutor vou passando os atributos de um objeto para dentro do record usando os metodos getters gerados pelo lombok dentro do Food{}
para isso dar certo eu tenho que colocar colado entre assinatura e anotações da classe Food{} estas outras anotações que são do lombok
public FoodResponseDTO( Food food){
this( food.getId(), food.getTitle(), food.getImage(), food.getPrice() );
}
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------
dentro desta classe marque qual metodo vai fazer mapeamento para requisição POST para esse endpoint food
@PostMapping public void saveFood(){
//resto do código
}
como vamos pegar esse body da requisição através de uma notação do Spring passado como parâmetro @RequestBody
essa notação avisa o spring para fazer injeção da depêndencia pegando o body do client POST e passar como parâmetro
@PostMapping
public void saveFood(@RequestBody FoodRequestDTO data){
}
definindo os parametros do record n precisa do id pois isso é responsabilidade do servidor gerar id automático e não do record
package com.example.cardapio_fullstack.food;
public record FoodRequestDTO( String title, String image, Integer price) {
}
usar o objeto repositoryque manipula os dados no banco , para receber por parâmetro o body do client e salvar na tupla do bd
repository.save(data);
public Food(FoodRequestDTO data) {
this.image = data.image();
this.title = data.title();
this.price = data.price();
}
@PostMapping
public void saveFood(@RequestBody FoodRequestDTO data){
Food foodData = new Food(data);
repository.save(foodData);
return;
}
http://localhost:8080/food
{
"price": 24,
"title": "hamburguer",
"image": "https://moinhoglobo.com.br/wp-content/uploads/2019/05/16-hamburguer.jpeg"
}
se retornar 200 ok , faça um get para mesma url para verificar se realmente foi adicionada no banco ou não
O tipo de ID gerado pelo servidor do tipo = print abaixo, não é válido para sistemas que vão para produção (um software de uma empresa)
pois ter definido a notação JPA para gerar sequencialmente os ID, não é seguro para o sistema privado.
Mais seguro usar tipo UUID para ninguém conseguir adivinhar o id de algum objeto que esta armazenado no banco, ou seja, chutar o id de algum dado e conseguir capturar essa informação privada de uma maneira pública não permitida.
@CrossOrigin(origins = "*",allowedHeaders = "*")
pode restringuir para dominio apenas da aplicação origins = "dominio-aplicacao" quando for fazer o deploy.
exemplo a minha aplicação esta rodando no meu localhost na porta 3000
até aqui 8h30
---------------------------------------------------------------------------------------------------------------------------------
Criar um componente que recebe como parametro um objeto que implemente uma interface para traduzir o que vier FoodRequestBody para dentro do componente card
para pegar os dados do backeend estando no frontend precisamos usar a lib react query tutorial1 tutorial2 tutorial kipper
npm install tanstack-query/react-query
####### se n funcionar testar este
npm i @tanstack/react-query
o file useFoodData.ts vai ser usado para fazer o fetch nos dados e salvar em uma variavel para conseguir usar
npm install axios
data: query.data?.data tem tudo isso explicado no video kipper
npm run dev
Precisa: ligar o pgAdmin, entrar com senha bd, conectar usuario e senha do banco do IntelliJ, executar backend no intelliJ , aí vejo se no navegador aparece igual a kipper dev
Fornercer um QueryClientPRovider empacotando componente principal da aplicação e passando um client pelo props
---------------------------------------------------------------------------------------------------------------------------------
em vez de criar várias tags repetidas de inputs, crie o componente de variavel constante Input e reutilize-o mudando apenas o props
onChange={event => updateValue(event.target.value)}
tem um problema pois o component reutilizável define que o updateValue somente receba string ou number e dentro do componente CreateModal estou passando como props para propriedade updateValue um tipo de variavel que não é nem string e nem é number , é uma função . Eu preciso definir um tipo de dado dentro do updateValue que posse aceiar receber por props aquela variável do state que tem um função dentro dela
/*
Já tentei usar generics e nao da certo pois eu preciso definir o tipo de dado para cada um dos inputs
*/
import { useState } from 'react';
interface InputProps{
label: string,
value: string | number,
updateValue: (value:string | number) => void; //primeira coisa digitada
//updateValue:(value: any)=> void;// solucao da dev kipper jeito do video dela
}
const Input = ({label, value, updateValue}:InputProps)=>{
return(
<>
<label>{label}</label>
<input value={value} onChange={event => updateValue(event.target.value)}></input>
</>
)
}
export function CreateModal(){
const [title, setTitle] = useState("");
const [price, setPrice] = useState(0);
const [image, setImage] = useState("");
return(
<div className="modal-ovelay"> // serve para escurecer pedaço atrás do modal
<div className="modal-body"> // dentro dessa div vai ficar o formulário
<h2>Cadastre um novo item no cardápio</h2>
<form className="input-container">
//erro updateValue esta esperando receber numero ou string, estou passando por props uma função do hook useState
<Input label="title" value={title} updateValue={setTitle} />
<Input label="price" value={price} updateValue={setPrice} />
<Input label="image" value={image} updateValue={setImage} />
</form>
</div>
</div>
)
}
me ajudou a resolver o bug link video
depois de 10 tentativas a IA finalmente resolveu para mim chat gemini
elas só me deu esta sugestão pois eu misturei o conteúdo do video com uma tentativa que inventei na minha cabeça usando o generics T
import { useState } from 'react';
interface InputProps<T extends string | number> { // Use a generic type T limited to string or number
label: string;
value: T;
updateValue: (value: T) => void; // Update value argument matches generic type T
}
const Input = <T extends string | number>({ label, value, updateValue }: InputProps<T>) => {
return (
<>
<label>{label}</label>
<input value={value} onChange={(event) => updateValue(event.target.value as T)} />
</>
);
};
export function CreateModal() {
const [title, setTitle] = useState("");
const [price, setPrice] = useState(0);
const [image, setImage] = useState("");
return (
<div className="modal-ovelay">
<div className="modal-body">
<h2>Cadastre um novo item no cardápio</h2>
<form className="input-container">
<Input label="title" value={title} updateValue={setTitle} />
<Input label="price" value={price} updateValue={setPrice} />
<Input label="image" value={image} updateValue={setImage} />
</form>
</div>
</div>
);
}
---------------------------------------------------------------------------------------------------------------------------------
e colocar uma invalidação da query anterior, que está desatualizada após o metodo POST ser concluido
import axios,{ AxiosPromise} from "axios";
import { FoodData } from "../interface/FoodData";
import { useMutation, useQueryClient } from '@tanstack/react-query';
const API_URL = 'http://localhost:8080';
//vai retornar uma promisse vazia, pois só precisa retornar a confirmação que indique que de fato foi armazenado pelo backend o que usuario inputou
const postData = async (data:FoodData):AxiosPromise<void> => {
const response = axios.post(API_URL + '/food', data);
return response;
}
export function useFoodData(){
const queryClient = useQueryClient();
const mutate = useMutation({
mutationFn: postData,
retry:2, // diz quantas vezes tentar novamente antes de retonar algum erro
onSuccess : ()=>{ // se essa funcao der certo executar essa function
//invalide os gets antigos que tenham esta chave 'food-data' pois o get deve trazer o dado atualizado
queryClient.invalidateQueries({ queryKey: ['food-data'] })
}
})
return mutate
}
---------------------------------------------------------------------------------------------------------------------------------
fazer a função submit() que pega os valores do formulário e envia para hook useFoodDataMutate.ts usando a variavél mutate
import { useState } from 'react';
import { useFoodDataMutate } from '../hooks/useFoodDataMutate';
import { FoodData } from '../interface/FoodData';
interface InputProps<T extends string | number> { // Use a generic type T limited to string or number
label: string;
value: T;
updateValue: (value: T) => void; // Update value argument matches generic type T
}
const Input = <T extends string | number>({ label, value, updateValue }: InputProps<T>) => {
//O <T extends string | number> é a parte que define o tipo genérico T.
return (
<>
<label>{label}</label>
<input value={value} onChange={(event) => updateValue(event.target.value as T)} />
</>
);
};
export function CreateModal() {
const [title, setTitle] = useState("");
const [price, setPrice] = useState(0);
const [image, setImage] = useState("");
const {mutate} = useFoodDataMutate();
// vai empurar aqueles dados do usuario obtidos no formulário para aquele pedaço do front end que vai fazer requisição http POST
const submit = ()=>{
const foodData:FoodData ={
title,
price,
image
}
mutate(foodData)
}
return (
<div className="modal-ovelay">
<div className="modal-body">
<h2>Cadastre um novo item no cardápio</h2>
<form className="input-container">
<Input label="title" value={title} updateValue={setTitle} />
<Input label="price" value={price} updateValue={setPrice} />
<Input label="image" value={image} updateValue={setImage} />
</form>
<button onClick={submit} className='btn-secondary'>postar</button>
</div>
</div>
);
}
---------------------------------------------------------------------------------------------------------------------------------
Chamar o componente create-modal.tsx dentro do componente App.tsx e escondê-lo quando não estiver aberto , e mostrar ele quando estiver aberto
vou colocar um <button> para usuário clicar e fazer executar a função que vai fazer aparecer o modal
import { useState } from 'react';
import './App.css'
import {Card} from './components/card/card'
import { useFoodData } from './components/hooks/useFoodData'
import { CreateModal } from './components/create-modal/create-modal';
function App() {
const { data } = useFoodData();
// criado novo estado
const [ isModalOpen, setIsModalOpen] = useState(false);
const handleOpenModal = () => {
//vai pegar o valor previo e vai mudar estado
setIsModalOpen( prev => !prev)
}
return (
<div className="container">
<h1>Cardápio</h1>
<div className="card-grid">
{data?.map(foodData =>
<Card
price={foodData.price}
title={foodData.title}
image={foodData.image}
/>
)}
</div>
{/* eu so vou mostrar o meu modal quando isModalOpen for true
se for true irei fazer aparecer o componente modal */}
{isModalOpen && <CreateModal/>}
<button onClick={handleOpenModal}>novo</button>
</div>
)
}
export default App
---------------------------------------------------------------------------------------------------------------------------------
.modal-overlay{
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.4);
overflow: hidden;
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.modal-overlay .modal-body{
background-color: white;
padding: 24px;
height: 60%;
width: 60%;
border-radius: 24px;
display: flex;
align-items: flex-start;
flex-direction: column;
justify-content: space-between;
}
.modal-overlay .modal-body h2{
font-size: 32px;
}
.modal-overlay .modal-body .input-container{
width: calc(100% - 24px);
}
.modal-overlay .modal-body input{
padding: 12px;
border: 2px solid #c6c5c5c5;
color: rgba(0, 0, 0, 0.9);
font-size: 18px;
line-height: 24px;
border-radius: 12px;
width: 100%;
margin-bottom: 12px;
}
.modal-overlay .modal-body label{
color: #242424;
font-weight: 600;
margin-bottom: 8px;
font-size: 18px;
}
.modal-overlay .modal-body .btn-secondary{
position: initial;
width: 100%;
margin-top: 32px;
}
.modal-overlay .modal-body .btn-secondary:hover{
background-color: #3a44f8;
transform: scale(1);
}
.modal-overlay{
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.4);
overflow: hidden;
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.modal-body{
background-color: white;
padding: 24px;
height: 60%;
width: 60%;
border-radius: 24px;
display: flex;
align-items: flex-start;
flex-direction: column;
justify-content: space-between;
}
.modal-body h2{
font-size: 32px;
}
.modal-body .input-container{
width: calc(100% - 24px);
}
.modal-body input{
padding: 12px;
border: 2px solid #c6c5c5c5;
color: rgba(0, 0, 0, 0.9);
font-size: 18px;
line-height: 24px;
border-radius: 12px;
width: 100%;
margin-bottom: 12px;
}
.modal-body label{
color: #242424;
font-weight: 600;
margin-bottom: 8px;
font-size: 18px;
}
.modal-body .btn-secondary{
position: initial;
width: 100%;
margin-top: 32px;
}
.modal-body .btn-secondary:hover{
background-color: #3a44f8;
transform: scale(1);
}
até agora foi gastado 11h20' em 36' de video ... fiz igual a kipper dev e apareceu um erro , demorei 2h , apareceu outro erro














