Skip to content

Commit 4fb27bc

Browse files
authored
Melhorias de arquitetura no frontend e implementaçao do layout de usuário logado (#156)
## Resumo Este PR reorganiza o frontend para uma arquitetura orientada a domínio (DDD), separando responsabilidades por contexto de negócio e reduzindo o acoplamento entre páginas, componentes, hooks e infraestrutura. Também implementa o novo layout autenticado da aplicação, com estrutura fixa de sidebar, header e área principal com scroll isolado. ## Principais mudanças - Reestruturação do frontend seguindo DDD: - `app` para composição da aplicação e rotas - `domains/auth` para autenticação - `domains/jobs` para vagas - `domains/marketing` para landing page - `shared` para componentes, hooks, libs e assets reutilizáveis - Implementação do layout autenticado: - Sidebar modular seguindo o design do Figma - Header modular seguindo o design do Figma - Área principal renderizada via `Outlet` - Header e sidebar fixos na tela - Scroll aplicado apenas no conteúdo principal - Ajustes de fluxo: - Login e OAuth redirecionam para `/home` - Página de vagas renomeada para `JobsPage` - Rotas autenticadas organizadas como `/home`, `/vagas` e `/mentoria` - Melhorias de tema: - Sidebar e header agora respeitam light/dark mode usando os tokens do projeto - Título do header muda dinamicamente conforme a rota atual ## Validação - Lint executado com sucesso - Testes unitários executados com sucesso - Build de produção executado com sucesso
2 parents f20a738 + 28dee09 commit 4fb27bc

95 files changed

Lines changed: 1244 additions & 601 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/ARCHITECTURE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Frontend Architecture
2+
3+
The frontend is organized around domain boundaries instead of technical file types.
4+
5+
## Layers
6+
7+
- `src/app`: application composition, providers, routing, and app-level pages.
8+
- `src/domains/<domain>/domain`: business types and pure rules without React or HTTP.
9+
- `src/domains/<domain>/application`: React hooks and use-cases that orchestrate domain rules.
10+
- `src/domains/<domain>/infrastructure`: API gateways and transport-specific code.
11+
- `src/domains/<domain>/presentation`: pages and components owned by that domain.
12+
- `src/shared`: reusable UI primitives, assets, hooks, and technical utilities.
13+
14+
## Domains
15+
16+
- `auth`: session state, credentials/OAuth API access, login/register/callback screens.
17+
- `jobs`: job entities, filtering/deduplication/pagination rules, scraper/job API access, dashboard UI.
18+
- `marketing`: public landing page sections.
19+
20+
## Import policy
21+
22+
Code should import from `@/app`, `@/domains`, or `@/shared`. Legacy technical folders such as `src/components`, `src/pages`, `src/hooks`, `src/services`, `src/context`, `src/lib`, and `src/types` were removed to keep a single domain-oriented structure.

frontend/components.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
"iconLibrary": "lucide",
1414
"rtl": false,
1515
"aliases": {
16-
"components": "@/components",
17-
"utils": "@/lib/utils",
18-
"ui": "@/components/ui",
19-
"lib": "@/lib",
20-
"hooks": "@/hooks"
16+
"components": "@/shared/ui",
17+
"utils": "@/shared/lib/utils",
18+
"ui": "@/shared/ui",
19+
"lib": "@/shared/lib",
20+
"hooks": "@/shared/hooks"
2121
},
2222
"menuColor": "default",
2323
"menuAccent": "subtle",

frontend/src/App.tsx

Lines changed: 0 additions & 60 deletions
This file was deleted.

frontend/src/app/App.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useState, useEffect } from "react";
2+
import { AppRoutes } from "@/app/AppRoutes";
3+
import Loading from "@/shared/ui/Loading";
4+
5+
function App() {
6+
const [appCarregando, setAppCarregando] = useState(true);
7+
8+
useEffect(() => {
9+
const timer = setTimeout(() => {
10+
setAppCarregando(false);
11+
}, 2000);
12+
13+
return () => clearTimeout(timer);
14+
}, []);
15+
16+
if (appCarregando) {
17+
return <Loading />;
18+
}
19+
20+
return (
21+
<AppRoutes />
22+
);
23+
}
24+
25+
export default App;

frontend/src/app/AppProviders.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { AuthProvider } from "@/domains/auth/application/AuthContext";
2+
import type { ReactNode } from "react";
3+
import { BrowserRouter } from "react-router-dom";
4+
5+
export function AppProviders({ children }: { children: ReactNode }) {
6+
return (
7+
<BrowserRouter>
8+
<AuthProvider>{children}</AuthProvider>
9+
</BrowserRouter>
10+
);
11+
}

frontend/src/app/AppRoutes.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useAuth } from "@/domains/auth/application/AuthContext";
2+
import { AuthenticatedLayout } from "@/app/AuthenticatedLayout";
3+
import AuthCallbackPage from "@/domains/auth/presentation/pages/AuthCallbackPage";
4+
import LoginPage from "@/domains/auth/presentation/pages/LoginPage";
5+
import RegisterPage from "@/domains/auth/presentation/pages/RegisterPage";
6+
import JobsPage from "@/domains/jobs/presentation/pages/JobsPage";
7+
import LandingPage from "@/domains/marketing/presentation/pages/LandingPage";
8+
import NotFound from "@/app/NotFound";
9+
import Loading from "@/shared/ui/Loading";
10+
import { Navigate, Route, Routes } from "react-router-dom";
11+
12+
function ProtectedRoute({ children }: { children: React.ReactNode }) {
13+
const { user, isLoading } = useAuth();
14+
if (isLoading) return <Loading />;
15+
if (!user) return <Navigate to="/login" replace />;
16+
return <>{children}</>;
17+
}
18+
19+
function PublicRoute({ children }: { children: React.ReactNode }) {
20+
const { user, isLoading } = useAuth();
21+
if (isLoading) return <Loading />;
22+
if (user) return <Navigate to="/home" replace />;
23+
return <>{children}</>;
24+
}
25+
26+
export function AppRoutes() {
27+
return (
28+
<Routes>
29+
<Route path="/" element={<LandingPage />} />
30+
<Route
31+
element={
32+
<ProtectedRoute>
33+
<AuthenticatedLayout />
34+
</ProtectedRoute>
35+
}
36+
>
37+
<Route path="/home" element={<JobsPage />} />
38+
<Route path="/dashboard" element={<NotFound />} />
39+
<Route path="/vagas" element={<JobsPage />} />
40+
<Route path="/mentoria" element={<NotFound />} />
41+
</Route>
42+
<Route
43+
path="/login"
44+
element={
45+
<PublicRoute>
46+
<LoginPage />
47+
</PublicRoute>
48+
}
49+
/>
50+
<Route
51+
path="/register"
52+
element={
53+
<PublicRoute>
54+
<RegisterPage />
55+
</PublicRoute>
56+
}
57+
/>
58+
<Route path="/auth/callback" element={<AuthCallbackPage />} />
59+
<Route path="*" element={<NotFound />} />
60+
</Routes>
61+
);
62+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useAuth } from "@/domains/auth/application/AuthContext";
2+
import { useTheme } from "@/shared/hooks/useTheme";
3+
import { ThemeToggle } from "@/shared/ui/theme-toggle";
4+
import { Bell, Mail } from "lucide-react";
5+
import { useLocation } from "react-router-dom";
6+
7+
const routeTitles: Array<{ path: string; title: string; exact?: boolean }> = [
8+
{ path: "/home", title: "Início", exact: true },
9+
{ path: "/vagas", title: "Vagas" },
10+
{ path: "/mentoria", title: "Mentoria" },
11+
];
12+
13+
function getHeaderTitle(pathname: string) {
14+
const matchedRoute = routeTitles.find((route) => {
15+
if (pathname === route.path) return true;
16+
if (route.exact) return false;
17+
return pathname.startsWith(`${route.path}/`);
18+
});
19+
20+
return matchedRoute?.title ?? "Início";
21+
}
22+
23+
function getInitials(value: string) {
24+
const normalized = value.trim();
25+
if (!normalized) return "U";
26+
27+
const [first = "", second = ""] = normalized
28+
.replace(/@.*/, "")
29+
.split(/\s|[._-]/)
30+
.filter(Boolean);
31+
32+
return `${first[0] ?? ""}${second[0] ?? first[1] ?? ""}`.toUpperCase();
33+
}
34+
35+
export function AuthenticatedHeader() {
36+
const { user } = useAuth();
37+
const { resolvedTheme, toggleTheme } = useTheme();
38+
const location = useLocation();
39+
40+
const displayName =
41+
user?.name || user?.displayName || user?.email || "Usuário";
42+
const title = getHeaderTitle(location.pathname);
43+
const initials = getInitials(displayName);
44+
45+
return (
46+
<header className="flex h-[103px] shrink-0 items-center justify-between border-b border-border bg-card px-6 text-card-foreground shadow-[0_8px_18px_rgba(0,0,0,0.04)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.22)] md:px-[43px]">
47+
<h1 className="truncate text-[32px] font-bold leading-[1.5] text-foreground">
48+
{title}
49+
</h1>
50+
51+
<div className="flex shrink-0 items-center gap-[17px]">
52+
<ThemeToggle theme={resolvedTheme} onToggle={toggleTheme} />
53+
<button
54+
type="button"
55+
className="flex size-6 items-center justify-center text-foreground transition-colors hover:text-primary"
56+
aria-label="Mensagens"
57+
>
58+
<Mail className="size-4" strokeWidth={2.4} />
59+
</button>
60+
<button
61+
type="button"
62+
className="flex size-6 items-center justify-center text-foreground transition-colors hover:text-primary"
63+
aria-label="Notificações"
64+
>
65+
<Bell className="size-4" strokeWidth={2.4} />
66+
</button>
67+
<div
68+
className="flex size-8 items-center justify-center rounded-full bg-[#8fb3a0] text-base font-semibold leading-6 text-primary dark:bg-primary/30 dark:text-primary-foreground"
69+
aria-label={displayName}
70+
title={displayName}
71+
>
72+
{initials}
73+
</div>
74+
</div>
75+
</header>
76+
);
77+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AuthenticatedHeader } from "@/app/AuthenticatedHeader";
2+
import { AuthenticatedSidebar } from "@/app/AuthenticatedSidebar";
3+
import { Outlet } from "react-router-dom";
4+
5+
export function AuthenticatedLayout() {
6+
return (
7+
<div className="flex h-screen overflow-hidden bg-background text-foreground">
8+
<AuthenticatedSidebar />
9+
10+
<div className="flex min-w-0 flex-1 flex-col">
11+
<AuthenticatedHeader />
12+
13+
<main className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden bg-background">
14+
<Outlet />
15+
</main>
16+
</div>
17+
</div>
18+
);
19+
}

0 commit comments

Comments
 (0)