Pular para o conteúdo principal

Estrutura do Frontend

O frontend segue uma arquitetura em camadas inspirada em Clean Architecture, separando claramente as responsabilidades de apresentação, lógica de negócio e infraestrutura.

Camadas da Aplicação

┌─────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Pages │ │ Components │ │ Theme │ │
│ │ (Rotas) │ │ (UI Blocks) │ │ (Layouts) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ APPLICATION LAYER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Queries │ │ Hooks │ │ Schemas │ │
│ │(React Query)│ │ (Custom) │ │ (Zod) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ INFRASTRUCTURE LAYER │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ API │ │ Axios │ │
│ │ (Clients) │ │ (Config) │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ STORE LAYER │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Zustand (Global State) │ │
│ │ auth | user | onboarding | solicitation │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Presentation Layer

Pages

Páginas organizadas por tipo de usuário e funcionalidade:

pages/
├── login/
│ ├── LoginPage.tsx
│ ├── EmailNotVerified.tsx
│ └── ResendVerification.tsx

├── register/
│ ├── index.tsx # Wizard de registro
│ ├── steps/
│ │ ├── EntrepreneurStep.tsx
│ │ └── StructuringAgentStep.tsx
│ └── schemas/

└── private/
├── admin/ # Gestor
│ ├── dashboard/
│ ├── business-plan/
│ ├── mentor-requests/
│ └── profile/

├── agent/ # Agente Estruturador
│ ├── Page.tsx # Kanban Board
│ ├── metrics/
│ ├── profile/
│ └── solicitacoes/

└── user/ # Empreendedor
├── solicitations/
├── credit-lines/
└── profile/

Components

Componentes reutilizáveis organizados por categoria:

components/
├── common/
│ ├── Alert/
│ ├── DialogComponent/
│ ├── FileUpload/
│ ├── InputComponent/
│ ├── Loading/
│ ├── PaginationWithInfo/
│ ├── ProfilePage/
│ ├── RichTextEditor/ # Editor Tiptap
│ ├── Search/
│ ├── SelectComponent/
│ ├── Table/
│ ├── TextAreaComponent/
│ └── Toast/

├── graphs/ # Componentes Recharts
│ ├── BarChart/
│ ├── PieChart/
│ └── LineChart/

├── KanbanBoard/ # Kanban com dnd-kit
│ ├── Board.tsx
│ ├── Column.tsx
│ └── Card.tsx

└── Onboarding/ # Wizard de registro
├── Stepper.tsx
└── StepContent.tsx

Theme

Configuração de tema e layouts:

theme/
├── colors/
│ └── index.ts # Paleta de cores

├── layout/
│ ├── Header/
│ ├── AdminLayout/ # Layout do Gestor
│ ├── AgentLayout/ # Layout do Agente
│ └── UserLayout/ # Layout do Empreendedor

└── index.ts # MUI Theme config

Application Layer

Queries (React Query)

queries/
├── agent/
│ ├── useAgentMetrics.ts
│ └── useAgentBoard.ts

├── business-plans/
│ ├── useBusinessPlans.ts
│ ├── useBusinessPlan.ts
│ └── useCreateBusinessPlan.ts

├── manager/
│ ├── useApprovalRequests.ts
│ └── useManagerMetrics.ts

├── structuring-agent/
│ └── useStructuringAgents.ts

└── schemas/
├── businessPlanSchema.ts
└── userSchema.ts

Exemplo de Query Hook

// queries/business-plans/useBusinessPlans.ts
import { useQuery } from "@tanstack/react-query";
import { businessPlansApi } from "@/infrastructure/api/business-plans";

export const useBusinessPlans = (filters?: BusinessPlanFilters) => {
return useQuery({
queryKey: ["business-plans", filters],
queryFn: () => businessPlansApi.list(filters),
staleTime: 5 * 60 * 1000, // 5 minutos
});
};

Infrastructure Layer

API Clients

api/
├── auth/
│ └── auth-api.ts # Login, register, verify

├── business-plans/
│ └── business-plans-api.ts

├── profile/
│ └── profile-api.ts

├── register/
│ └── register-api.ts

├── agent/
│ └── metrics/
│ └── metrics-api.ts

├── manager/
│ └── metrics/
│ └── metrics-api.ts

├── structuring-agent/
│ └── structuring-agent-api.ts

└── util/
└── http.ts # Axios config

Configuração do Axios

// lib/axios.ts
import axios from "axios";
import { useAuthStore } from "@/store/auth/authStore";

const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
"Content-Type": "application/json",
},
});

// Interceptor de request para adicionar token
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().accessToken;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});

// Interceptor de response para refresh token
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Lógica de refresh token
}
return Promise.reject(error);
}
);

export default api;

Store Layer (Zustand)

store/
├── auth/
│ └── authStore.ts # Tokens JWT

├── user/
│ └── userStore.ts # Dados do usuário

├── onboarding/
│ └── onboardingStore.ts # Estado do wizard

├── agentBoardStore.ts # Estado do Kanban
├── solicitationStore.ts # Solicitações
├── pendingCorrectionStore.ts

└── utils/
└── formatCurrency.ts

Exemplo de Store

// store/user/userStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";

interface UserState {
user: User | null;
role: "entrepreneur" | "agent" | "manager" | null;
setUser: (user: User) => void;
clearUser: () => void;
}

export const useUserStore = create<UserState>()(
persist(
(set) => ({
user: null,
role: null,
setUser: (user) => set({ user, role: user.role }),
clearUser: () => set({ user: null, role: null }),
}),
{
name: "user-storage",
}
)
);

Routes

routes/
├── index.tsx # Router principal
├── adminRoutes.tsx # /admin/*
├── agentRoutes.tsx # /agent/*
├── userRoutes.tsx # /user/*
├── loginRoutes.tsx # /login, /register
└── auth/
└── PrivateRoute.tsx # Proteção de rotas

Proteção de Rotas

// routes/auth/PrivateRoute.tsx
import { Navigate } from "react-router-dom";
import { useUserStore } from "@/store/user/userStore";

interface PrivateRouteProps {
children: React.ReactNode;
allowedRoles?: ("entrepreneur" | "agent" | "manager")[];
}

export const PrivateRoute: FC<PrivateRouteProps> = ({ children, allowedRoles }) => {
const { user, role } = useUserStore();

if (!user) {
return <Navigate to="/login" />;
}

if (allowedRoles && !allowedRoles.includes(role!)) {
return <Navigate to="/unauthorized" />;
}

return <>{children}</>;
};