This document provides comprehensive instructions for AI agents (like Copilot, Claude, ChatGPT, or custom LLM agents) working on the Sogrub e-commerce application. Follow these guidelines to ensure consistent, high-quality contributions.
Sogrub is a Next.js 16 application for selling restored furniture. The platform allows:
- Public users: Browse and view product catalog
- Admins: Authenticate and manage products (CRUD operations)
- Backend: Supabase (PostgreSQL + Storage + Auth)
- Package Manager: pnpm (NOT npm or yarn)
- Read existing code before making changes
- Check
types/product.tsfor data structures - Review
lib/supabase/for database operations - Examine
components/for reusable UI patterns
- Never use
anytype - preferunknownor proper typing - All functions must have explicit return types
- Props interfaces must be defined before components
- Use Zod for runtime validation on forms
- Default to Server Components
- Use
'use client'only when needed:- React hooks (useState, useEffect, etc.)
- Browser APIs (localStorage, window, etc.)
- Event handlers (onClick, onChange, etc.)
- Third-party libraries requiring client-side
// ✅ Correct - Server Component
import { createClient } from "@/lib/supabase/server";
async function Page() {
const supabase = await createClient();
const { data, error } = await supabase.from("products").select("*");
if (error) {
console.error("Error:", error);
return <ErrorComponent />;
}
return <ProductList products={data} />;
}
// ✅ Correct - Client Component
("use client");
import { createClient } from "@/lib/supabase/client";
function ClientComponent() {
const supabase = createClient();
// ... client-side logic
}- Define Types (
types/)
export interface NewFeature {
id: string;
name: string;
created_at: string;
}- Create Database Functions (
lib/supabase/)
export async function getNewFeatures(): Promise<NewFeature[]> {
try {
const supabase = await createClient();
const { data, error } = await supabase.from("new_feature").select("*");
if (error) throw error;
return data || [];
} catch (error) {
console.error("Error fetching features:", error);
return [];
}
}- Build UI Components (
components/features/)
interface FeatureCardProps {
feature: NewFeature;
}
export function FeatureCard({ feature }: FeatureCardProps) {
return (
<Card>
<CardHeader>
<CardTitle>{feature.name}</CardTitle>
</CardHeader>
</Card>
);
}- Create Page/Route (
app/features/page.tsx)
import { getNewFeatures } from "@/lib/supabase/features";
import { FeatureCard } from "@/components/features/FeatureCard";
export default async function FeaturesPage() {
const features = await getNewFeatures();
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-6">Features</h1>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<FeatureCard key={feature.id} feature={feature} />
))}
</div>
</div>
);
}Always use React Hook Form + Zod:
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const formSchema = z.object({
title: z.string().min(3, "El título debe tener al menos 3 caracteres"),
price: z.number().min(0, "El precio debe ser positivo"),
description: z.string().optional(),
});
type FormData = z.infer<typeof formSchema>;
export function MyForm() {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
price: 0,
description: "",
},
});
async function onSubmit(values: FormData) {
try {
// Handle submission
console.log(values);
} catch (error) {
console.error("Error submitting form:", error);
}
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>{/* Form fields */}</form>
</Form>
);
}// Using Next.js Image component
import Image from "next/image";
<Image
src={imageUrl}
alt="Descriptive text"
width={400}
height={300}
className="object-cover"
/>;
// Supabase Storage upload
const file = event.target.files[0];
const fileName = `${Date.now()}-${file.name}`;
const { error: uploadError } = await supabase.storage
.from("product-images")
.upload(fileName, file);
const {
data: { publicUrl },
} = supabase.storage.from("product-images").getPublicUrl(fileName);// Layout
<div className="container mx-auto px-4 py-8">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{/* Content */}
</div>
</div>
// Cards with hover effects
<Card className="overflow-hidden transition-all hover:shadow-lg">
{/* Content */}
</Card>
// Responsive text
<h1 className="text-2xl md:text-3xl lg:text-4xl font-bold">
Title
</h1>
// Buttons
<Button size="sm" variant="default">
Click Me
</Button>
// Conditional classes
import { cn } from "@/lib/utils";
<div className={cn(
"base-class",
isActive && "active-class",
isDisabled && "disabled-class"
)}>// error.tsx in any route
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="container mx-auto py-16 text-center">
<h2 className="text-2xl font-bold mb-4">Algo salió mal</h2>
<p className="text-muted-foreground mb-4">{error.message}</p>
<Button onClick={reset}>Intentar de nuevo</Button>
</div>
);
}// loading.tsx in any route
export default function Loading() {
return (
<div className="container mx-auto py-16">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 rounded w-1/4"></div>
<div className="h-64 bg-gray-200 rounded"></div>
</div>
</div>
);
}// Development only
if (process.env.NODE_ENV === "development") {
console.log("Debug info:", data);
}
// Errors (always log)
console.error("Error context:", error);- Never commit
.env.localor secrets - Validate all user input with Zod schemas
- Use Supabase RLS policies for data access
- Sanitize file uploads (check size, type, etc.)
- Use
NEXT_PUBLIC_only for truly public variables - Implement CSRF protection on forms
- Validate authentication state before admin actions
// Always optimize images
import Image from "next/image";
<Image
src={imageUrl}
alt="Product"
width={600}
height={400}
loading="lazy"
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>;// Use Suspense for streaming
import { Suspense } from "react";
export default function Page() {
return (
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
);
}
// Parallel data fetching
const [products, categories] = await Promise.all([
getAllProducts(),
getAllCategories(),
]);// Lazy load heavy components
import dynamic from "next/dynamic";
const HeavyComponent = dynamic(() => import("@/components/HeavyComponent"), {
loading: () => <p>Cargando...</p>,
});products (
id: uuid (PK)
title: text (NOT NULL)
description: text
materials: text
price: numeric (NOT NULL)
width: numeric
height: numeric
depth: numeric
image_url: text
created_at: timestamp
)- SELECT: Public (anyone can read)
- INSERT/UPDATE/DELETE: Authenticated users only
All user-facing text must be in Spanish (es-ES):
// ✅ Correct
<Button>Añadir al carrito</Button>
<p>No se encontraron productos</p>
// ❌ Incorrect
<Button>Add to cart</Button>
<p>No products found</p>
// Currency formatting
const formatPrice = (price: number) => {
return new Intl.NumberFormat("es-ES", {
style: "currency",
currency: "EUR",
}).format(price);
};
// Date formatting
const formatDate = (date: string) => {
return new Intl.DateTimeFormat("es-ES", {
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date(date));
};- Component renders without errors
- Forms validate correctly
- Error states display properly
- Loading states work
- Mobile responsive (375px, 768px, 1024px, 1440px)
- Images load and display correctly
- Links navigate properly
- Accessibility (keyboard navigation, ARIA labels)
- TypeScript has no errors
- No
anytypes used - Proper error handling implemented
- Comments added for complex logic
- Unused imports removed
- Console.logs removed (except error logging)
- Follows project naming conventions
# Development
pnpm dev # Start dev server (localhost:3000)
pnpm build # Build for production
pnpm start # Start production server
pnpm lint # Run ESLint
# Supabase
supabase start # Start local Supabase
supabase db reset # Reset local database
supabase migration new <name> # Create new migration| File | Purpose |
|---|---|
types/product.ts |
Product interface definition |
lib/supabase/client.ts |
Client-side Supabase instance |
lib/supabase/server.ts |
Server-side Supabase instance |
lib/supabase/products.ts |
Product database operations |
lib/utils.ts |
Utility functions (cn, etc.) |
components/ui/ |
Reusable UI components |
app/layout.tsx |
Root layout with metadata |
app/admin/page.tsx |
Admin dashboard example |
supabase/schema.sql |
Database schema and RLS |
- Never use default exports - always use named exports
- Server Components by default - add
'use client'only when necessary - Type everything - avoid
any, use proper TypeScript - Handle errors gracefully - never let errors crash the app
- Spanish language - all UI text must be in Spanish
- Mobile-first - design for mobile, enhance for desktop
- Accessibility - always include alt text, ARIA labels
- Security - validate input, respect RLS policies
When you're unsure about an implementation:
- Check existing code - Is there a similar pattern in the codebase?
- Follow Next.js conventions - What does Next.js recommend?
- Prioritize type safety - Can this be more strongly typed?
- Consider performance - Will this impact load time?
- Think about UX - Is this intuitive for Spanish-speaking users?
- Security first - Could this introduce a vulnerability?
Final Note: When in doubt, prefer simplicity and maintainability over cleverness. Write code that others (including your future self) can understand and modify easily.