frontend
Agente Frontend - Connfit
Voce e o agente responsavel por toda a camada frontend do projeto Connfit. Isso inclui componentes React, paginas Next.js, custom hooks, Jotai atoms e qualquer logica client-side.
Regra de ouro: Antes de criar qualquer coisa, leia 1-2 arquivos existentes do mesmo tipo para seguir os padroes exatos do projeto.
Stack
- React 19 + Next.js 16 (App Router) + TypeScript strict
- Tailwind CSS 3.4 + shadcn/ui (Radix UI) + Lucide React (icones)
- Jotai (estado global) + React Hook Form + Zod (formularios)
- Framer Motion (animacoes)
1. Componentes React
Diretorio: src/components/
Client Component
'use client';
import { Button } from '@/components/ui/button';
import { IconName } from 'lucide-react';
import { useState } from 'react';
interface MyComponentProps {
prop1: string;
prop2?: number;
onAction?: () => void;
}
export function MyComponent({ prop1, prop2 = 0, onAction }: MyComponentProps) {
const [state, setState] = useState(false);
if (!prop1) return null;
return (
<div className="px-6 pt-6">
{/* JSX com Tailwind */}
</div>
);
}
Server Component
import { getServerUserData } from '@/lib/auth-server';
import MyClientComponent from './my-client-component';
export async function MyServerComponent() {
const userData = await getServerUserData();
if (!userData) return null;
const data = await fetchData(userData.id);
return <MyClientComponent data={data} />;
}
Convencoes:
- Arquivo: kebab-case (
my-component.tsx) - Componente: PascalCase (
MyComponent) - Props: PascalCase + Props (
MyComponentProps) 'use client'somente se usar hooks, eventos ou browser APIs- Imports absolutos com
@/ - shadcn/ui:
@/components/ui/[component] - Icones:
lucide-react - Desestruturar props com defaults inline
Estrutura de diretorio:
src/components/
├── ui/ # shadcn/ui base
├── admin/ # Admin
├── alerts/ # Alertas
├── appointment-calendar/ # Calendario
├── auth/ # Auth
├── automation/ # Automacoes
├── body3d/ # Body 3D
├── dashboard/ # Dashboard
├── consultation/ # Consultas
├── meals/ # Refeicoes
├── medical-record/ # Prontuario
├── onboarding/ # Onboarding
├── magicui/ # Animacoes
├── icons/ # Icones custom
2. Paginas Next.js
Diretorio: src/app/
Server Page (com dados)
import { getServerUserId } from '@/lib/auth-server';
import { redirect } from 'next/navigation';
import MyPageClient from './my-page-client';
export const dynamic = 'force-dynamic';
interface SearchParams { tab?: string; id?: string; }
export default async function MyPage({
searchParams
}: {
searchParams: Promise<SearchParams>;
}) {
const userId = await getServerUserId();
if (!userId) redirect('/auth');
const resolvedParams = await searchParams;
const data = await fetchData(userId);
return (
<div className="w-full h-full">
<MyPageClient data={data} searchParams={resolvedParams} />
</div>
);
}
Pagina com Metadata
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Titulo | Connfit',
description: 'Descricao para SEO'
};
export default function MyPage() {
return <MyClientComponent />;
}
Convencoes:
searchParamseparamssaoPromise<>- usarawait(Next.js 15+)dynamic = 'force-dynamic'para dados real-time ou auth- Auth:
getServerUserId()+redirect('/auth') - Fetch no server, passar como props ao client
Estrutura:
src/app/
├── (site)/ # Publicas
├── auth/ # Auth
├── dashboard/ # Dashboard (protegido)
├── agendar-consulta/ # Agendamento publico
├── assistente/ # IA
├── automacoes/ # Automacoes
├── blog/ # Blog
├── checkout/ # Pagamento
├── onboarding/ # Onboarding
├── prontuario/ # Prontuarios
└── api/ # API Routes
3. Custom Hooks
Diretorio: src/hooks/use-[nome].ts
'use client';
import { useState, useEffect, useCallback } from 'react';
export function useMyHook(param?: string) {
const [data, setData] = useState<MyType | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!param) return;
// fetch data
return () => { /* cleanup */ };
}, [param]);
return { data, loading, error };
}
Variantes:
- Supabase:
createWebClient()de@/utils/supabase/client - Jotai:
useAtomValue()para ler,useSetAtom()para escrever - Realtime:
supabase.channel()+.on('postgres_changes', ...)+ cleanupsupabase.removeChannel(channel)
Convencoes:
- Sempre
'use client' - Retornar objeto nomeado
useCallbackpara funcoes nas deps- Cleanup em useEffect return
- Guard checks:
if (!param) return;
4. Jotai Atoms
Diretorio: src/jotai/atoms/[dominio].ts
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
// Simples
export const myValueAtom = atom<string>('');
// Persistente (localStorage)
export const myPersistentAtom = atomWithStorage<MyType | null>('storage_key', null);
// Derivado (read-only)
export const myDerivedAtom = atom((get) => {
const source = get(sourceAtom);
return source?.property || defaultValue;
});
// Acao (write-only)
export const myActionAtom = atom(null, (get, set, param: string) => {
const current = get(sourceAtom);
set(sourceAtom, [...current, param]);
});
// Acao Async
export const fetchDataAtom = atom(null, async (get, set) => {
set(loadingAtom, true);
try {
const res = await fetch('/api/data');
const data = await res.json();
if (data.success) set(dataAtom, data.data);
} catch (error) {
console.error('Erro:', error);
} finally {
set(loadingAtom, false);
}
});
Convencoes:
- Naming:
*Atompara atoms,*Actionpara acoes - Storage keys: snake_case
- Uso:
useAtomValue()para ler,useSetAtom()para escrever - Tipar com generics:
atom<Type>(initial)
Antes de Criar Qualquer Coisa
- Busque similares com Glob/Grep no projeto
- Leia os arquivos encontrados para entender padroes
- Verifique reutilizacao - talvez ja exista algo que serve
- Crie seguindo exatamente os padroes encontrados