1- import { useEffect , useState } from "react" ;
2- import ProductCard from "../../components/ProductCard" ;
3- import ProductCardSkeleton from "../../components/ProductCardSkeleton" ;
1+ import { useState } from "react" ;
2+ import ProductCard from "../../components/ProductCard" ;
3+ import ProductCardSkeleton from "../../components/ProductCardSkeleton" ;
4+ import useProducts from "../../hooks/api/useProducts" ;
45
5- type Category = {
6- id : number ;
7- name : string ;
8- } ;
6+ export default function Home ( ) {
7+ const { products, loading, error } = useProducts ( ) ;
8+ const [ search , setSearch ] = useState ( "" ) ;
99
10- type Product = {
11- id : number ;
12- name : string ;
13- category ?: Category ;
14- price : number ;
15- } ;
10+ const filtered = products . filter ( ( p ) =>
11+ p . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ,
12+ ) ;
1613
17- export default function Home ( ) {
18- const [ products , setProducts ] = useState < Product [ ] > ( [ ] ) ;
19- const [ loading , setLoading ] = useState ( true ) ;
20- const [ error , setError ] = useState < string | null > ( null ) ;
21- const [ search , setSearch ] = useState ( "" ) ;
14+ return (
15+ < div className = "p-4" >
16+ { loading ? (
17+ < div >
18+ { /* Busca */ }
19+ < div className = "w-full h-11 bg-gray-200 rounded-xl mb-4 animate-pulse" > </ div >
2220
23- useEffect ( ( ) => {
24- async function fetchProducts ( ) {
25- try {
26- setLoading ( true ) ;
21+ { /* Título */ }
22+ < div className = "h-6 w-28 bg-gray-200 rounded mb-2 animate-pulse" > </ div >
2723
28- await new Promise ( ( resolve ) => setTimeout ( resolve , 3000 ) ) ;
24+ { /* Quantidade */ }
25+ < div className = "h-4 w-16 bg-gray-200 rounded mb-4 animate-pulse" > </ div >
2926
30- const res = await fetch ( "http://localhost:8080/products" ) ;
31-
32- if ( ! res . ok ) throw new Error ( "Erro ao buscar produtos" ) ;
33-
34- const data : Product [ ] = await res . json ( ) ;
35- setProducts ( data ) ;
36- } catch ( err : unknown ) {
37- if ( err instanceof Error ) {
38- setError ( err . message ) ;
39- } else {
40- setError ( "Erro desconhecido" ) ;
41- }
42- } finally {
43- setLoading ( false ) ;
44- }
45- }
46-
47- fetchProducts ( ) ;
48- } , [ ] ) ;
49-
50- const filtered = products . filter ( ( p ) =>
51- p . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ,
52- ) ;
53-
54- return (
55- < div className = "p-4" >
56- { loading ? (
57- < div >
58- { /* Busca */ }
59- < div className = "w-full h-11 bg-gray-200 rounded-xl mb-4 animate-pulse" > </ div >
60-
61- { /* Título */ }
62- < div className = "h-6 w-28 bg-gray-200 rounded mb-2 animate-pulse" > </ div >
63-
64- { /* Quantidade */ }
65- < div className = "h-4 w-16 bg-gray-200 rounded mb-4 animate-pulse" > </ div >
66-
67- { /* Cards */ }
68- < div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
69- { Array . from ( { length : 4 } ) . map ( ( _ , index ) => (
70- < ProductCardSkeleton key = { index } />
71- ) ) }
72- </ div >
73- </ div >
74- ) : (
75- < >
76- { /* Busca */ }
77- < input
78- type = "text"
79- placeholder = "Buscar produtos..."
80- className = "w-full bg-surface border border-border rounded-xl px-3 py-2 mb-4"
81- value = { search }
82- onChange = { ( e ) => setSearch ( e . target . value ) }
83- />
84-
85- { /* Título */ }
86- < h2 className = "text-lg font-semibold" > Produtos</ h2 >
87-
88- { /* Quantidade */ }
89- < p className = "text-sm text-gray-500 mb-3" >
90- { filtered . length } itens
91- </ p >
92-
93- { /* ERRO */ }
94- { error && (
95- < p className = "text-red-500" > { error } </ p >
96- ) }
97-
98- { /* Nenhum produto */ }
99- { filtered . length === 0 && ! error && (
100- < p > Nenhum produto encontrado.</ p >
101- ) }
102-
103- { /* LISTA */ }
104- { ! error && filtered . length > 0 && (
27+ { /* Cards */ }
10528 < div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
106- { filtered . map ( ( product ) => (
107- < ProductCard
108- key = { product . id }
109- product = { product }
110- />
29+ { Array . from ( { length : 4 } ) . map ( ( _ , index ) => (
30+ < ProductCardSkeleton key = { index } />
11131 ) ) }
11232 </ div >
113- ) }
114- </ >
115- ) }
116- </ div >
117- ) ;
118- }
33+ </ div >
34+ ) : (
35+ < >
36+ { ! error && (
37+ < >
38+ { /* Busca */ }
39+ < input
40+ type = "text"
41+ placeholder = "Buscar produtos..."
42+ className = "w-full bg-surface border border-border rounded-xl px-3 py-2 mb-4"
43+ value = { search }
44+ onChange = { ( e ) => setSearch ( e . target . value ) }
45+ />
46+
47+ { /* Título */ }
48+ < h2 className = "text-lg font-semibold" > Produtos</ h2 >
49+
50+ { /* Quantidade */ }
51+ < p className = "text-sm text-gray-500 mb-3" >
52+ { filtered . length } itens
53+ </ p >
54+ </ >
55+ ) }
56+
57+ { /* ERRO */ }
58+ { error ? (
59+ < div className = "space-y-4" >
60+ < h2 className = "text-lg font-semibold" > Produtos</ h2 >
61+ < p className = "text-red-500" > { error } </ p >
62+ < div className = "grid grid-cols-2 gap-3" >
63+ { Array . from ( { length : 4 } ) . map ( ( _ , index ) => (
64+ < ProductCardSkeleton key = { index } />
65+ ) ) }
66+ </ div >
67+ </ div >
68+ ) : (
69+ < >
70+ { /* Nenhum produto */ }
71+ { filtered . length === 0 && < p > Nenhum produto encontrado.</ p > }
72+
73+ { /* LISTA */ }
74+ { filtered . length > 0 && (
75+ < div className = "grid grid-cols-2 lg:grid-cols-4 gap-3" >
76+ { filtered . map ( ( product ) => (
77+ < ProductCard key = { product . id } product = { product } />
78+ ) ) }
79+ </ div >
80+ ) }
81+ </ >
82+ ) }
83+ </ >
84+ ) }
85+ </ div >
86+ ) ;
87+ }
0 commit comments