Spaces:
Running
Running
Rename index.html to app/page.tsx
Browse files- app/page.tsx +81 -0
- index.html +0 -19
app/page.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// app/page.tsx
|
| 2 |
+
import { Suspense } from 'react';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
import { ProductGrid } from '@/components/ui/product-grid';
|
| 5 |
+
import { ProductGridSkeleton } from '@/components/ui/skeletons';
|
| 6 |
+
import { HeroSection } from '@/components/marketing/hero';
|
| 7 |
+
import { db } from '@/lib/db'; // Drizzle ORM setup
|
| 8 |
+
|
| 9 |
+
// 🔐 [SHIFT-LEFT SECURITY]: Schema Zod para validar a fronteira de dados do banco
|
| 10 |
+
// "Parse, Don't Validate"
|
| 11 |
+
const ProductSchema = z.object({
|
| 12 |
+
id: z.string().uuid(),
|
| 13 |
+
name: z.string().min(1),
|
| 14 |
+
price: z.number().positive(),
|
| 15 |
+
imageUrl: z.string().url(),
|
| 16 |
+
slug: z.string(),
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
type Product = z.infer<typeof ProductSchema>;
|
| 20 |
+
|
| 21 |
+
// ⚡ [OTIMIZAÇÃO DE PERFORMANCE]: Revalidação de cache a cada 60 segundos (ISR)
|
| 22 |
+
export const revalidate = 60;
|
| 23 |
+
|
| 24 |
+
// Função pura para buscar dados, operando exclusivamente no servidor
|
| 25 |
+
async function getFeaturedProducts(): Promise<Product[]> {
|
| 26 |
+
try {
|
| 27 |
+
// 🎯 [DECISÃO ARQUITETURAL]: Drizzle ORM para consultas SQL tipadas e baixo overhead
|
| 28 |
+
const rawProducts = await db.query.products.findMany({
|
| 29 |
+
limit: 12,
|
| 30 |
+
where: (products, { eq }) => eq(products.isFeatured, true),
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
// Validação estrita: se o DB retornar dados corrompidos, a página quebra graciosamente,
|
| 34 |
+
// evitando injetar dados maliciosos ou nulos na UI.
|
| 35 |
+
return z.array(ProductSchema).parse(rawProducts);
|
| 36 |
+
} catch (error) {
|
| 37 |
+
console.error('Falha ao buscar produtos:', error);
|
| 38 |
+
// Early return com fallback array para não derrubar a página inteira
|
| 39 |
+
return [];
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// ♿ [A11y & SEO STRICT]: Uso de tags semânticas (main, section) e meta dados corretos
|
| 44 |
+
export default async function StorefrontIndex() {
|
| 45 |
+
const productsPromise = getFeaturedProducts();
|
| 46 |
+
|
| 47 |
+
return (
|
| 48 |
+
<main className="flex min-h-screen flex-col items-center justify-between">
|
| 49 |
+
<HeroSection
|
| 50 |
+
title="Nova Coleção 2026"
|
| 51 |
+
subtitle="A performance que você exige. O design que você merece."
|
| 52 |
+
/>
|
| 53 |
+
|
| 54 |
+
<section
|
| 55 |
+
aria-labelledby="featured-products-heading"
|
| 56 |
+
className="w-full max-w-7xl px-4 py-12 md:px-8"
|
| 57 |
+
>
|
| 58 |
+
<h2 id="featured-products-heading" className="text-3xl font-bold tracking-tight mb-8">
|
| 59 |
+
Destaques da Semana
|
| 60 |
+
</h2>
|
| 61 |
+
|
| 62 |
+
{/* ⚡ [OTIMIZAÇÃO DE PERFORMANCE/INP]: Streaming UI com Suspense.
|
| 63 |
+
O usuário vê o esqueleto enquanto o banco de dados resolve a query. */}
|
| 64 |
+
<Suspense fallback={<ProductGridSkeleton count={12} />}>
|
| 65 |
+
<FeaturedProducts loader={productsPromise} />
|
| 66 |
+
</Suspense>
|
| 67 |
+
</section>
|
| 68 |
+
</main>
|
| 69 |
+
);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// Componente assíncrono isolado para consumir a promise
|
| 73 |
+
async function FeaturedProducts({ loader }: { loader: Promise<Product[]> }) {
|
| 74 |
+
const products = await loader;
|
| 75 |
+
|
| 76 |
+
if (products.length === 0) {
|
| 77 |
+
return <p className="text-muted-foreground">Nenhum produto encontrado no momento.</p>;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
return <ProductGrid products={products} />;
|
| 81 |
+
}
|
index.html
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width" />
|
| 6 |
-
<title>My static Space</title>
|
| 7 |
-
<link rel="stylesheet" href="style.css" />
|
| 8 |
-
</head>
|
| 9 |
-
<body>
|
| 10 |
-
<div class="card">
|
| 11 |
-
<h1>Welcome to your static Space!</h1>
|
| 12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
-
<p>
|
| 14 |
-
Also don't forget to check the
|
| 15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
-
</p>
|
| 17 |
-
</div>
|
| 18 |
-
</body>
|
| 19 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|