From 61b3af4c53fbb4ed256d08cd3b916fe9c149565a Mon Sep 17 00:00:00 2001 From: Artur Oliveira Date: Tue, 16 Dec 2025 13:54:33 -0300 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20componentizar=20header=20?= =?UTF-8?q?e=20modais=20-=20Extrai=20brand,=20a=C3=A7=C3=B5es=20e=20modais?= =?UTF-8?q?=20para=20componentes=20dedicados=20-=20Mant=C3=A9m=20Header=20?= =?UTF-8?q?como=20orquestrador=20de=20estado=20e=20integra=20novos=20tipos?= =?UTF-8?q?=20-=20Atualiza=20AGENTS.md=20exigindo=20componentiza=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20interfaces=20complexas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + frontend/src/components/Header.tsx | 245 +++--------------- .../src/components/header/HeaderActions.tsx | 46 ++++ .../src/components/header/HeaderBrand.tsx | 18 ++ .../src/components/header/ProfileModal.tsx | 90 +++++++ .../src/components/header/ServerModal.tsx | 117 +++++++++ frontend/src/components/header/types.ts | 19 ++ 7 files changed, 329 insertions(+), 207 deletions(-) create mode 100644 frontend/src/components/header/HeaderActions.tsx create mode 100644 frontend/src/components/header/HeaderBrand.tsx create mode 100644 frontend/src/components/header/ProfileModal.tsx create mode 100644 frontend/src/components/header/ServerModal.tsx create mode 100644 frontend/src/components/header/types.ts diff --git a/AGENTS.md b/AGENTS.md index 2ac6773..6cd941e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ Orientações rápidas para agentes ou automações que atuam neste repositório - Em componentes React com Tailwind, mova classnames para uma constante `Styles` ao final do arquivo sempre que um elemento tiver mais de 5 classes (referência: `Login.tsx`). - Em componentes React com Tailwind, mova classnames para uma constante `Styles` ao final do arquivo sempre que um elemento tiver mais de 5 classes (referência: `Login.tsx`). - Quando houverem classnames que se repetem muitas vezes em vários elementos/componentes, mova esses grupos repetidos para a constante `Styles` mesmo que possuam menos de 5 classes — isso ajuda a evitar duplicação e facilita manutenção. +- Sempre que uma view ou componente começar a crescer demais, quebre a interface em subcomponentes menores e reutilizáveis (ex: dividir headers em partes especializadas) antes de seguir evoluindo o layout. ## Padrão para mensagens de commit **Instrução:** Gere um comando `git commit -m` completo, em português, seguindo o padrão **Conventional Commits**, com base no `diff` abaixo. diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index d12307e..68a8127 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,8 +1,13 @@ import { type ChangeEvent, type FormEvent, useEffect, useState } from "react"; import toast from "react-hot-toast"; import api from "../Api"; +import { HeaderActions } from "./header/HeaderActions"; +import { HeaderBrand } from "./header/HeaderBrand"; +import { ProfileModal } from "./header/ProfileModal"; +import { ServerModal } from "./header/ServerModal"; import type { Applications, DatabaseType, ServersType } from "../types/enums"; import type { User } from "../types/User"; +import type { ProfileFormState, ServerFormState } from "./header/types"; type ModalType = "addServer" | "editProfile" | null; @@ -13,24 +18,6 @@ interface HeaderProps { onProfileUpdated?: (user: User) => void; } -type ServerFormState = { - name: string; - ip: string; - port: string; - user: string; - password: string; - type: ServersType; - application: Applications; - dbType: DatabaseType; -}; - -type ProfileFormState = { - firstName: string; - lastName: string; - email: string; - password: string; -}; - const defaultServerForm: ServerFormState = { name: "", ip: "", @@ -83,13 +70,13 @@ export const Header = ({ currentUser, userError, onServerCreated, onProfileUpdat setProfileForm((prev) => ({ ...prev, password: "" })); }; - const handleServerFormChange = (e: ChangeEvent) => { - const { name, value } = e.target; + const handleServerFormChange = (event: ChangeEvent) => { + const { name, value } = event.target; setServerForm((prev) => ({ ...prev, [name]: value })); }; - const handleProfileFormChange = (e: ChangeEvent) => { - const { name, value } = e.target; + const handleProfileFormChange = (event: ChangeEvent) => { + const { name, value } = event.target; setProfileForm((prev) => ({ ...prev, [name]: value })); }; @@ -141,200 +128,44 @@ export const Header = ({ currentUser, userError, onServerCreated, onProfileUpdat } }; - const renderModalContent = () => { - if (activeModal === "addServer") { - return ( -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- ); - } - - if (activeModal === "editProfile") { - if (userError) { - return

{userError}

; - } - - return ( -
-
-
- - -
-
- - -
-
-
- - -
-
- - -

Informe uma nova senha para confirmar a alteração.

-
-
- - -
-
- ); - } - - return null; - }; - return ( <>
-
- Logo Hit Communications -
-

Hit Communications

-

Servers Manager

-
-
- -
-
- - {isMenuOpen && ( -
- - -
- )} -
- -
+ + openModal("addServer")} + onEditProfile={() => openModal("editProfile")} + />
- {activeModal && ( -
-
-
-

- {activeModal === "addServer" ? "Adicionar novo servidor" : "Editar perfil"} -

- -
-
{renderModalContent()}
-
-
- )} + + + ); }; const Styles = { wrapper: "flex items-center justify-between rounded-xl border border-cardBorder bg-card px-6 py-4 shadow-sm", - brand: "flex items-center gap-3", - logo: "h-10 w-10 object-contain", - title: "text-base font-semibold text-text", - subtitle: "text-xs uppercase tracking-wide text-text-secondary", - actions: "flex items-center gap-3", - menuTrigger: "rounded-lg border border-cardBorder bg-white/70 px-4 py-2 text-sm font-medium text-text flex items-center gap-2 hover:bg-white transition-colors", - dropdown: "absolute right-0 mt-2 w-48 rounded-lg border border-cardBorder bg-white py-2 shadow-lg z-10", - dropdownItem: "w-full px-4 py-2 text-left text-sm text-text-secondary hover:bg-bg hover:text-text transition-colors", - logoutButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-hover", - modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4", - modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl", - modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder", - modalTitle: "text-lg font-semibold text-text", - closeButton: "text-2xl leading-none text-text-secondary hover:text-text", - modalBody: "pt-4", - form: "space-y-4", - formGrid: "grid gap-4 md:grid-cols-2", - field: "flex flex-col gap-2", - label: "text-xs font-semibold uppercase tracking-wide text-text-secondary", - input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent", - select: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize", - helperText: "text-xs text-text-secondary", - modalActions: "flex justify-end gap-3 pt-2", - secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50", - primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70", }; diff --git a/frontend/src/components/header/HeaderActions.tsx b/frontend/src/components/header/HeaderActions.tsx new file mode 100644 index 0000000..c9a3c1b --- /dev/null +++ b/frontend/src/components/header/HeaderActions.tsx @@ -0,0 +1,46 @@ +interface HeaderActionsProps { + isMenuOpen: boolean; + onToggleMenu: () => void; + onAddServer: () => void; + onEditProfile: () => void; + onLogout?: () => void; +} + +export const HeaderActions = ({ isMenuOpen, onToggleMenu, onAddServer, onEditProfile, onLogout }: HeaderActionsProps) => { + return ( +
+
+ + {isMenuOpen && ( +
+ + +
+ )} +
+ +
+ ); +}; + +const Styles = { + actions: "flex items-center gap-3", + menuTrigger: "rounded-lg border border-cardBorder bg-white/70 px-4 py-2 text-sm font-medium text-text flex items-center gap-2 hover:bg-white transition-colors", + dropdown: "absolute right-0 mt-2 w-48 rounded-lg border border-cardBorder bg-white py-2 shadow-lg z-10", + dropdownItem: "w-full px-4 py-2 text-left text-sm text-text-secondary hover:bg-bg hover:text-text transition-colors", + logoutButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-hover", +}; diff --git a/frontend/src/components/header/HeaderBrand.tsx b/frontend/src/components/header/HeaderBrand.tsx new file mode 100644 index 0000000..dea23b1 --- /dev/null +++ b/frontend/src/components/header/HeaderBrand.tsx @@ -0,0 +1,18 @@ +export const HeaderBrand = () => { + return ( +
+ Logo Hit Communications +
+

Hit Communications

+

Servers Manager

+
+
+ ); +}; + +const Styles = { + brand: "flex items-center gap-3", + logo: "h-10 w-10 object-contain", + title: "text-base font-semibold text-text", + subtitle: "text-xs uppercase tracking-wide text-text-secondary", +}; diff --git a/frontend/src/components/header/ProfileModal.tsx b/frontend/src/components/header/ProfileModal.tsx new file mode 100644 index 0000000..e4e54f7 --- /dev/null +++ b/frontend/src/components/header/ProfileModal.tsx @@ -0,0 +1,90 @@ +import type { ChangeEvent, FormEvent } from "react"; +import type { User } from "../../types/User"; +import type { ProfileFormState } from "./types"; + +interface ProfileModalProps { + isOpen: boolean; + currentUser: User | null; + userError: string | null; + form: ProfileFormState; + loading: boolean; + onClose: () => void; + onChange: (event: ChangeEvent) => void; + onSubmit: (event: FormEvent) => void; +} + +export const ProfileModal = ({ + isOpen, + currentUser, + userError, + form, + loading, + onClose, + onChange, + onSubmit, +}: ProfileModalProps) => { + if (!isOpen) return null; + + const isDisabled = !currentUser; + + return ( +
+
+
+

Editar perfil

+ +
+ {userError ? ( +

{userError}

+ ) : ( +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +

Informe uma nova senha para confirmar a alteração.

+
+
+ + +
+
+ )} +
+
+ ); +}; + +const Styles = { + modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4", + modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl", + modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder", + modalTitle: "text-lg font-semibold text-text", + closeButton: "text-2xl leading-none text-text-secondary hover:text-text", + helperText: "pt-4 text-sm text-text-secondary", + form: "pt-4 space-y-4", + formGrid: "grid gap-4 md:grid-cols-2", + field: "flex flex-col gap-2", + label: "text-xs font-semibold uppercase tracking-wide text-text-secondary", + input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent disabled:opacity-70", + modalActions: "flex justify-end gap-3 pt-2", + secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50", + primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70", +}; diff --git a/frontend/src/components/header/ServerModal.tsx b/frontend/src/components/header/ServerModal.tsx new file mode 100644 index 0000000..e189297 --- /dev/null +++ b/frontend/src/components/header/ServerModal.tsx @@ -0,0 +1,117 @@ +import type { ChangeEvent, FormEvent } from "react"; +import type { Applications, DatabaseType, ServersType } from "../../types/enums"; +import type { ServerFormState } from "./types"; + +interface ServerModalProps { + isOpen: boolean; + form: ServerFormState; + loading: boolean; + onClose: () => void; + onChange: (event: ChangeEvent) => void; + onSubmit: (event: FormEvent) => void; + serverTypeOptions: ServersType[]; + applicationOptions: Applications[]; + databaseOptions: DatabaseType[]; +} + +export const ServerModal = ({ + isOpen, + form, + loading, + onClose, + onChange, + onSubmit, + serverTypeOptions, + applicationOptions, + databaseOptions, +}: ServerModalProps) => { + if (!isOpen) return null; + + return ( +
+
+
+

Adicionar novo servidor

+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ ); +}; + +const Styles = { + modalOverlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4", + modal: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl", + modalHeader: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder", + modalTitle: "text-lg font-semibold text-text", + closeButton: "text-2xl leading-none text-text-secondary hover:text-text", + form: "pt-4 space-y-4", + formGrid: "grid gap-4 md:grid-cols-2", + field: "flex flex-col gap-2", + label: "text-xs font-semibold uppercase tracking-wide text-text-secondary", + input: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent", + select: "rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize", + modalActions: "flex justify-end gap-3 pt-2", + secondaryButton: "rounded-md border border-cardBorder px-4 py-2 text-sm font-medium text-text hover:bg-bg disabled:opacity-50", + primaryButton: "rounded-md bg-accent px-4 py-2 text-sm font-semibold text-white hover:bg-hover disabled:opacity-70", +}; diff --git a/frontend/src/components/header/types.ts b/frontend/src/components/header/types.ts new file mode 100644 index 0000000..58733b1 --- /dev/null +++ b/frontend/src/components/header/types.ts @@ -0,0 +1,19 @@ +import type { Applications, DatabaseType, ServersType } from "../../types/enums"; + +export type ServerFormState = { + name: string; + ip: string; + port: string; + user: string; + password: string; + type: ServersType; + application: Applications; + dbType: DatabaseType; +}; + +export type ProfileFormState = { + firstName: string; + lastName: string; + email: string; + password: string; +};