From 45042c2d113ccd35c4ffd15e6cedc139058488b4 Mon Sep 17 00:00:00 2001 From: Diogo Munhoz Fraga Date: Sun, 22 Feb 2026 16:27:07 -0300 Subject: [PATCH] feat(portal): align navbar and sidebar with figma style, add notifications dropdown, use english for UI - Logo: compact style with "by Grid Labs", restore Tron cube icon - Navbar: org selector next to logo with vertical bar, notification bell with empty-state dropdown - Sidebar: search bar in menu, dashboard icon and label, figma-style link styling - All user-facing portal texts in English --- .gitignore | 3 +- portal/src/components/AdminRoute.tsx | 4 +- portal/src/components/DataTable.tsx | 4 +- portal/src/components/ProtectedRoute.tsx | 2 +- portal/src/pages/Login.tsx | 2 +- portal/src/pages/clusters/Clusters.tsx | 2 +- portal/src/pages/users/Users.tsx | 2 +- portal/src/shared/components/DataTable.tsx | 2 +- portal/src/shared/components/Layout.tsx | 240 +++++++++++---------- portal/src/shared/components/Logo.tsx | 82 +++---- 10 files changed, 173 insertions(+), 170 deletions(-) diff --git a/.gitignore b/.gitignore index d0cd796..2021b9b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ docker/.env .idea/ .vscode/ -coverage/ \ No newline at end of file +coverage/ +figma/ \ No newline at end of file diff --git a/portal/src/components/AdminRoute.tsx b/portal/src/components/AdminRoute.tsx index e35b6f8..79ba79b 100644 --- a/portal/src/components/AdminRoute.tsx +++ b/portal/src/components/AdminRoute.tsx @@ -13,7 +13,7 @@ export function AdminRoute({ children }: AdminRouteProps) {
-

Carregando...

+

Loading...

) @@ -25,7 +25,7 @@ export function AdminRoute({ children }: AdminRouteProps) {

Acesso Negado

-

Você não tem permissão para acessar esta página.

+

You do not have permission to access this page.

) diff --git a/portal/src/components/DataTable.tsx b/portal/src/components/DataTable.tsx index 5976932..fbde5ff 100644 --- a/portal/src/components/DataTable.tsx +++ b/portal/src/components/DataTable.tsx @@ -32,7 +32,7 @@ function DataTable({ getRowKey, actions, searchable = true, - searchPlaceholder = 'Buscar...', + searchPlaceholder = 'Search...', }: DataTableProps) { const [searchTerm, setSearchTerm] = useState('') const [sortColumn, setSortColumn] = useState(null) @@ -193,7 +193,7 @@ function DataTable({
- Carregando... + Loading...
diff --git a/portal/src/components/ProtectedRoute.tsx b/portal/src/components/ProtectedRoute.tsx index 7f40822..4f735f0 100644 --- a/portal/src/components/ProtectedRoute.tsx +++ b/portal/src/components/ProtectedRoute.tsx @@ -13,7 +13,7 @@ export function ProtectedRoute({ children }: ProtectedRouteProps) {
-

Carregando...

+

Loading...

) diff --git a/portal/src/pages/Login.tsx b/portal/src/pages/Login.tsx index e46c0c4..52c39d0 100644 --- a/portal/src/pages/Login.tsx +++ b/portal/src/pages/Login.tsx @@ -129,7 +129,7 @@ export default function Login() { if (errors.email) setErrors({ ...errors, email: '' }) }} className={`input pl-10 w-full ${errors.email ? 'border-red-500' : ''}`} - placeholder="seu@email.com" + placeholder="your@email.com" autoComplete="email" /> {errors.email && ( diff --git a/portal/src/pages/clusters/Clusters.tsx b/portal/src/pages/clusters/Clusters.tsx index 12635d7..07645b7 100644 --- a/portal/src/pages/clusters/Clusters.tsx +++ b/portal/src/pages/clusters/Clusters.tsx @@ -390,7 +390,7 @@ function Clusters() { value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500/50 focus:border-blue-400 transition-all text-sm" - placeholder="meu-cluster" + placeholder="my-cluster" required /> diff --git a/portal/src/pages/users/Users.tsx b/portal/src/pages/users/Users.tsx index 814f1d3..10d43b3 100644 --- a/portal/src/pages/users/Users.tsx +++ b/portal/src/pages/users/Users.tsx @@ -397,7 +397,7 @@ function Users() { value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} className="input w-full" - placeholder="usuario@exemplo.com" + placeholder="user@example.com" required /> diff --git a/portal/src/shared/components/DataTable.tsx b/portal/src/shared/components/DataTable.tsx index 7be3f09..065e73d 100644 --- a/portal/src/shared/components/DataTable.tsx +++ b/portal/src/shared/components/DataTable.tsx @@ -32,7 +32,7 @@ function DataTable({ getRowKey, actions, searchable = true, - searchPlaceholder = 'Buscar...', + searchPlaceholder = 'Search...', }: DataTableProps) { const [searchTerm, setSearchTerm] = useState('') const [sortColumn, setSortColumn] = useState(null) diff --git a/portal/src/shared/components/Layout.tsx b/portal/src/shared/components/Layout.tsx index 2a03529..38d839d 100644 --- a/portal/src/shared/components/Layout.tsx +++ b/portal/src/shared/components/Layout.tsx @@ -1,7 +1,7 @@ import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom' import { useState, useRef, useEffect } from 'react' import { - Home, + LayoutDashboard, Cloud, Globe, AppWindow, @@ -15,6 +15,7 @@ import { Users, Key, Search, + Bell, ChevronLeft, ChevronRight, Building2, @@ -26,7 +27,7 @@ import { useOrganization } from '../../contexts/OrganizationContext' import { APP_VERSION } from '../../config/version' const generalNavItems = [ - { label: 'Home', path: '/', icon: Home }, + { label: 'Dashboard', path: '/', icon: LayoutDashboard }, { label: 'Applications', path: '/applications', icon: AppWindow }, ] @@ -70,6 +71,7 @@ function Layout() { const [searchQuery, setSearchQuery] = useState('') const [orgSelectorOpen, setOrgSelectorOpen] = useState(false) const [userMenuOpen, setUserMenuOpen] = useState(false) + const [notificationsOpen, setNotificationsOpen] = useState(false) const location = useLocation() const navigate = useNavigate() const { user, logout, isLoading: isAuthLoading } = useAuth() @@ -78,6 +80,7 @@ function Layout() { const hasNoOrganizations = !isAuthLoading && user && (!user.organizations || user.organizations.length === 0) const orgSelectorRef = useRef(null) const userMenuRef = useRef(null) + const notificationsRef = useRef(null) const handleLogout = () => { logout() @@ -118,6 +121,23 @@ function Layout() { } }, [userMenuOpen]) + // Close notifications dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (notificationsRef.current && !notificationsRef.current.contains(event.target as Node)) { + setNotificationsOpen(false) + } + } + + if (notificationsOpen) { + document.addEventListener('mousedown', handleClickOutside) + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [notificationsOpen]) + const selectedOrganization = organizations.find(org => org.uuid === selectedOrganizationUuid) const canManageOrg = Boolean(selectedOrganization?.is_owner || selectedOrganization?.is_admin) @@ -153,64 +173,42 @@ function Layout() { return (
- {/* Header */} -
-
-
- - -
+ {/* Header - style from figma: logo | bar | org selector | search | user */} +
+
+ + {user && ( -
-
+ <> +
+
{orgSelectorOpen && organizations.length > 0 && ( -
-
+
+
Organizations @@ -240,10 +238,45 @@ function Layout() {
)}
+ + )} +
-
+ {user && ( +
+
+ + {notificationsOpen && ( +
+
+ + Notifications + +
+
+ No new notifications. +
+
+ )} +
+
{userMenuOpen && ( -
-
+
+
Account @@ -292,9 +325,8 @@ function Layout() {
)}
-
- )} -
+
+ )}
@@ -302,27 +334,27 @@ function Layout() {
)} -
    +
      {filteredOrgItems.map((item) => { const Icon = item.icon const isActive = location.pathname === item.path @@ -387,20 +413,14 @@ function Layout() { to={item.path} onClick={() => setSidebarOpen(false)} className={` - flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-xl - transition-all duration-200 - ${ - isActive - ? 'bg-gradient-primary text-white shadow-soft font-medium' - : 'text-neutral-600 hover:bg-neutral-50 hover:text-primary-600' - } + flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} px-3 py-2 rounded-lg text-sm transition-colors + ${isActive + ? 'bg-primary-50 text-primary-600 font-medium' + : 'text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900'} `} > - - {!sidebarCollapsed && {item.label}} + + {!sidebarCollapsed && {item.label}} ) @@ -420,7 +440,7 @@ function Layout() {
)} -
    +
      {filteredAdministrativeItems.map((item) => { const Icon = item.icon const isActive = location.pathname === item.path @@ -430,20 +450,14 @@ function Layout() { to={item.path} onClick={() => setSidebarOpen(false)} className={` - flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-xl - transition-all duration-200 - ${ - isActive - ? 'bg-gradient-primary text-white shadow-soft font-medium' - : 'text-neutral-600 hover:bg-neutral-50 hover:text-primary-600' - } + flex items-center ${sidebarCollapsed ? 'justify-center' : 'gap-3'} px-3 py-2 rounded-lg text-sm transition-colors + ${isActive + ? 'bg-primary-50 text-primary-600 font-medium' + : 'text-neutral-600 hover:bg-neutral-100 hover:text-neutral-900'} `} > - - {!sidebarCollapsed && {item.label}} + + {!sidebarCollapsed && {item.label}} ) @@ -453,19 +467,19 @@ function Layout() { )} - {/* Collapse Button */} -
      + {/* Collapse Button - style from figma */} +
      diff --git a/portal/src/shared/components/Logo.tsx b/portal/src/shared/components/Logo.tsx index fd50c8b..1e41628 100644 --- a/portal/src/shared/components/Logo.tsx +++ b/portal/src/shared/components/Logo.tsx @@ -2,56 +2,44 @@ import { Link } from 'react-router-dom' export function Logo() { return ( - - {/* Logo Icon - Design sofisticado com formas geométricas */} -
      -
      -
      - - {/* Design sofisticado: Cubo 3D estilizado representando infraestrutura/plataforma */} - - - - {/* Linha decorativa */} - - -
      + +
      + + + + + +
      - {/* Texto do logo */}
      - - Tron - - - Platform - + Tron + by Grid Labs
      ) } -