vek1-frontend
Você escreve a camada de UI do vek1 (Next.js App Router). Regra de ouro: leia 1-2
arquivos existentes do mesmo tipo antes de criar algo novo — os exemplos abaixo são o
padrão real do repo, não uma sugestão genérica.
Fato mais importante: isto é um BFF, não um app com DB direto
O Next.js do vek1 não escreve no Postgres diretamente. src/lib/db/schema.ts existe
só para tipagem/Drizzle-kit (migrations via schema-migrator). Toda leitura/escrita real
de dados de produto (documents, products, agents, orders...) passa porsrc/lib/api-client/* (apiClient.<recurso>.*), que chama o backend externo vek1-api
via HTTP (callApi em src/lib/api-client/http.ts). Se você está prestes a escreverdb.insert(...) ou db.select(...) dentro de uma server action ou API route de produto,
pare — isso é o padrão errado copiado de outro projeto. Exceção: código de auth
(src/lib/auth.ts/auth-server.ts) usa Drizzle direto porque é a única coisa que este
banco realmente possui em Better Auth.
1. Server Actions
Diretório: src/app/actions/*.ts. Exemplo real: src/app/actions/document-actions.ts.
'use server';
import { revalidatePath } from 'next/cache';
import { apiClient } from '@/lib/api-client';
import { getCurrentUser } from '@/lib/auth-server';
export async function createDocument(data: CreateDocumentData) {
try {
const user = await getCurrentUser();
if (!user) return { success: false, error: 'Unauthorized' };
const doc = await apiClient.documents.create({ ...data }, user.id);
revalidatePath('/documents');
return { success: true, data: doc };
} catch (error) {
console.error('Error creating document:', error);
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
Convenções:
- Retorno sempre
{ success: boolean, error?: string, data?: T }(inglês nas chaves, PT-BR
nos comentários/mensagens de UI é opcional conforme o arquivo). - Auth:
getCurrentUser()de@/lib/auth-server, checar!userantes de qualquer coisa. - Toda mutação real delega pro
apiClient.<recurso>.*— nunca reimplemente a chamada HTTP. revalidatePath()nos paths afetados depois de mutar.- Try/catch em toda função exportada, log com
console.errordescritivo.
2. API Routes
Diretório: src/app/api/**/route.ts. Exemplo real:src/app/api/stores/[storeId]/documents/route.ts.
import { connection, NextRequest, NextResponse } from 'next/server';
import { apiClient } from '@/lib/api-client';
import { getCurrentUser } from '@/lib/auth-server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ storeId: string }> }
) {
await connection();
try {
const { storeId } = await params;
const user = await getCurrentUser();
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const docs = await apiClient.documents.list({ store_id: storeId, limit: 200 }, user.id);
return NextResponse.json({ documents: docs });
} catch (error) {
console.error('Error fetching documents:', error);
return NextResponse.json({ error: 'Erro interno do servidor' }, { status: 500 });
}
}
Convenções:
params/searchParamssãoPromise<>— sempreawait.await connection()no topo de rotas dinâmicas com Cache Components (vervek1-stack-expertpras pegadinhas deconnection()+ prerender).- Se a rota expõe uma projeção "enxuta" de um tipo maior (como
documents), documente isso
no tipo do lado do client (versrc/hooks/use-documents.ts— comentário explícito sobre
o shape real vs. oDocumentcompleto do schema). Isso já causou um crash em produção
(KnowledgeBaseSelectlendodoc.tags/doc.contentque a API não mandava) — se adicionar
campo novo na projeção, atualize o tipo do hook junto, no mesmo PR.
3. Componentes React
Diretório: src/components/<domínio>/. src/components/ui/ é shadcn — sempre prefira
compor a partir de lá antes de criar algo do zero.
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
interface MyComponentProps {
value: string;
onChange: (value: string) => void;
}
export function MyComponent({ value, onChange }: MyComponentProps) {
const [open, setOpen] = useState(false);
// ...
}
Convenções:
- Arquivo kebab-case, componente PascalCase, props
<Nome>Props. 'use client'só se usa hooks/eventos/browser APIs — páginas e muito do data-fetching
ficam em Server Components.- Sem React Hook Form nem Framer Motion no projeto — não introduza essas libs num
componente novo sem confirmar com o usuário (não são dependências instaladas). Forms
usamuseStatesimples (vercreate-document-modal.tsx,edit-document-modal.tsx). - Ícones:
lucide-react. Classes: Tailwind v4 (tailwind.config.js+ tokens CSS emglobals.css— vervek1-ui-uxpara o design system).
4. Hooks
Diretório: src/hooks/use-*.ts. Exemplo real: src/hooks/use-documents.ts.
'use client';
import { useAtomValue } from 'jotai';
import { useCallback, useEffect, useState } from 'react';
import { selectedStoreAtom } from '@/jotai/stores';
export function useMyResource() {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const currentStore = useAtomValue(selectedStoreAtom);
const fetchData = useCallback(async () => {
if (!currentStore?.id) { setData([]); return; }
// fetch via /api/... (credentials: 'include') — hooks de client component chamam
// a API route interna, não o apiClient diretamente (apiClient é server-only).
}, [currentStore]);
useEffect(() => { fetchData(); }, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
Convenções:
- Hooks de client component não importam
@/lib/api-client(éimport 'server-only')
— eles batem em uma API route (fetch('/api/...', { credentials: 'include' })). - Guard checks cedo (
if (!currentStore?.id) return). - Retornar objeto nomeado com
loading/error/refetch.
5. Jotai Atoms
Diretório: src/jotai/<domínio>.ts. Exemplo real: src/jotai/stores.ts.
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
export const selectedStoreAtom = atomWithStorage<Tables<'stores'> | null>(
'selectedStore',
null
);
export const hasSelectedStoreAtom = atom(get => get(selectedStoreAtom) !== null);
Convenções: *Atom no nome, atomWithStorage para persistir entre sessões,useAtomValue/useSetAtom (evite useAtom completo se só precisa de um lado).
Antes de criar/alterar algo
- Leia 1-2 arquivos existentes do mesmo tipo (action, route, componente, hook, atom).
- Confira se o dado já vem de algum
apiClient.<recurso>existente antes de inventar uma
chamada nova emsrc/lib/api-client/. - Se a mudança tocar o schema Drizzle (
src/lib/db/schema.ts), isso é doschema-migrator
— não aplique migration você mesmo. - Se tocar Evolution/WhatsApp (
evolution-instance.ts,whatsapp-handler.ts, webhook),
delegue proevolution-debugger.