fix(): Ajuste no bloqueio de telas

This commit is contained in:
Kenio 2025-11-10 15:24:39 -03:00
parent 3114dbb6d7
commit 0d5d285786
15 changed files with 169 additions and 47 deletions

View file

@ -28,7 +28,7 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: 'SAAS - Orius Tecnologia',
title: 'Monitoring App - Orius Tecnologia',
description: 'Evolução tecnológica com toque humano',
icons: {
icon: '/images/favicon.ico',
@ -57,13 +57,13 @@ export default function RootLayout({
<BreadcrumbList>
<BreadcrumbItem className="hidden md:block">
<BreadcrumbLink href="#">
Building Your Application
Orius Tecnologia
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className="hidden md:block" />
<BreadcrumbItem>
<BreadcrumbPage>
Data Fetching
Monitoramento de Servidores
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>

View file

@ -0,0 +1,30 @@
import { NextRequest, NextResponse } from 'next/server';
import { jwtDecode } from 'jwt-decode';
import GetSigla from '@/shared/actions/text/GetSigla';
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
interface JwtPayload {
id: string;
iat: number;
exp: number;
data?: UserAuthenticatedInterface;
}
export async function GET(req: NextRequest) {
try {
const token = req.cookies.get('access_token')?.value;
if (!token) return NextResponse.json(null);
const decoded: JwtPayload = jwtDecode(token);
if (decoded.data && typeof decoded.data === 'string') {
decoded.data = JSON.parse(decoded.data);
}
if (decoded.data) {
decoded.data.sigla = GetSigla(decoded.data.nome || '');
}
return NextResponse.json(decoded);
} catch (err) {
return NextResponse.json(null);
}
}

View file

@ -21,7 +21,7 @@ import {
SidebarRail,
} from '@/components/ui/sidebar';
import useGUsuarioGetJWTHook from '@/shared/hooks/auth/useGUsuarioGetJWTHook';
import useUserGetJWTHook from '@/shared/hooks/auth/useUserGetJWTHook';
import Image from 'next/image';
// This is sample data.
@ -52,13 +52,11 @@ const data = {
},
],
},
],
projects: [],
]
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { userAuthenticated } = useGUsuarioGetJWTHook();
console.log('LOGADO', userAuthenticated)
const { userAuthenticated } = useUserGetJWTHook();
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
@ -78,10 +76,10 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold">
Orius Tecnologia
Monitoring App
</span>
<span className="">
25.9.1
v1.0
</span>
</div>
</a>
@ -91,7 +89,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />
<NavProjects projects={data.projects} />
</SidebarContent>
<SidebarFooter>
{userAuthenticated?.data ? <NavUser user={userAuthenticated.data} /> : 'Carregando...'}

View file

@ -43,7 +43,7 @@ export function NavMain({
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
<SidebarMenuButton tooltip={item.title} className='cursor-pointer'>
{item.icon && <item.icon />}
<span>{item.title}</span>

View file

@ -101,14 +101,14 @@ export function NavUser({ user }: { user: UserAuthenticatedInterface }) {
<DropdownMenuSeparator />
<DropdownMenuGroup>
{/* <DropdownMenuGroup>
<DropdownMenuItem className="cursor-pointer">
<Sparkles />
Configurações
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuSeparator /> */}
<DropdownMenuItem className="cursor-pointer" onClick={() => handleConfirmOpen()}>
<LogOut />

View file

@ -14,37 +14,44 @@ export function middleware(request: NextRequest) {
const publicRoute = publicRoutes.find((route) => route.path === path);
const authToken = request.cookies.get('access_token');
// 1. Não autenticado e rota pública → segue normal
// Evita loop — se já estiver na página de login, não redireciona novamente
if (path === ROOT_PATH) {
return NextResponse.next();
}
// 1 Não autenticado e rota pública → segue normal
if (!authToken && publicRoute) {
return NextResponse.next();
}
// 2. Não autenticado e rota privada → redireciona para raiz
// 2 Não autenticado e rota privada → redireciona para login
if (!authToken && !publicRoute) {
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
}
// 3. Autenticado em rota pública com flag "redirect" → vai para raiz
// 3 Autenticado em rota pública com flag "redirect" → vai para raiz
if (authToken && publicRoute && publicRoute.whenAuthenticated === 'redirect') {
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
}
// 4. Autenticado em rota privada → valida token
// 4 Autenticado em rota privada → valida token
if (authToken && !publicRoute) {
const decoded: any = jwt.decode(authToken.value);
// Token inválido → redireciona (uma vez)
if (!decoded || !decoded.exp) {
console.log('Token inválido');
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
}
// Verifica expiração
const currentTime = Math.floor(Date.now() / 1000);
if (decoded.exp <= currentTime) {
console.log('Token expirado');
return NextResponse.redirect(new URL(ROOT_PATH, request.url));
}
// Token válido → segue
return NextResponse.next();
}

View file

@ -42,7 +42,7 @@ export default function ClientTable({ data, pagination, onEdit, onDelete }: Clie
</TableHeader>
<TableBody>
{data.map((client) => (
{Array.isArray(data) && data.length > 0 ? ( data.map((client) => (
<TableRow key={client.client_id}>
<TableCell className="font-medium">{client.client_id}</TableCell>
<TableCell>
@ -89,13 +89,20 @@ export default function ClientTable({ data, pagination, onEdit, onDelete }: Clie
</DropdownMenu>
</TableCell>
</TableRow>
))}
))
) : (
<TableRow>
<TableCell colSpan={5} className="text-center text-muted-foreground">
Nenhum cliente encontrado.
</TableCell>
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={5}>
{/* Se existir paginação, mostra o total de registros; senão, usa o tamanho da lista */}
Total de clientes: {pagination?.total_records ?? data.length}
Total de clientes: {pagination?.total_records ?? data?.length}
</TableCell>
</TableRow>
</TableFooter>

View file

@ -134,7 +134,7 @@ export default function UserForm({ isOpen, data, onClose, onSave, buttonIsLoadin
{/* Campo: Cargo */}
<FormField
control={form.control}
name="cargo"
name="position"
render={({ field }) => (
<FormItem>
<FormLabel>Cargo</FormLabel>

View file

@ -6,6 +6,7 @@ import { AuthenticateUserInterface } from '@/shared/interfaces/AuthenticateUserI
import { withClientErrorHandler } from '@/withClientErrorHandler/withClientErrorHandler';
export default async function UserLoginData(form: any) {
const api = new API();
const response = await api.send({

View file

@ -43,7 +43,10 @@ export function useUserFormHook(data: FormValues | null) {
useEffect(() => {
if (data) {
// Modo edição → carrega dados do usuário
form.reset(data);
form.reset({
...data,
password: '',
});
console.log('Form carregado com dados:', data);
} else {
// Modo criação → limpa o formulário
@ -53,7 +56,7 @@ export function useUserFormHook(data: FormValues | null) {
email: '',
password: '',
team: '',
cargo: '',
position: '',
status: SituacoesEnum.ATIVO,
user_id_create: null,
user_id_update: null,

View file

@ -5,8 +5,8 @@ export const UserSchema = z.object({
user_id: z.number().optional(),
name: z.string().min(3, { message: "O nome deve ter no mínimo 3 caracteres." }),
email: z.email({ message: "Por favor, insira um email válido." }),
password: z.string().min(6, { message: "A senha deve ter pelo menos 6 caracteres." }),
cargo: z.string().nullable().optional(),
password: z.string().min(6, { message: "A senha deve ter pelo menos 6 caracteres." }).optional().or(z.literal("")),
position: z.string().nullable().optional(),
team: z.string().min(1, { message: "A equipe é obrigatória." }),
status: z.enum(SituacoesEnum), // 'A' ou 'I'
date_register: z.string().optional(),

View file

@ -23,25 +23,28 @@ export default async function UserLoginService(form: any) {
console.log("service", response);
// Verifica se o usuário foi encontrado (caso contrário, retorna erro 404)
if (response.data.user_id <= 0) {
if (response?.data?.user_id <= 0) {
return {
code: 404, // Código de erro HTTP simulando "não encontrado"
message: 'Não foi localizado o usuário', // Mensagem informativa ao cliente
};
} else {
// Obtém o manipulador de cookies do contexto do servidor
const cookieStore = await cookies();
// Define um cookie com o token de autenticação
cookieStore.set('access_token', response?.data?.token, {
httpOnly: true, // Impede acesso via JavaScript (segurança contra XSS)
secure: process.env.NODE_ENV === 'production', // Garante HTTPS em produção
sameSite: 'strict', // Restringe envio de cookies entre domínios
path: '/', // Torna o cookie acessível em toda a aplicação
maxAge: 60 * 60 * 24, // Define validade de 24 horas (em segundos)
});
// Redireciona o usuário autenticado para a página administrativa de usuários
redirect('/administrativo/clientes');
}
// Obtém o manipulador de cookies do contexto do servidor
const cookieStore = await cookies();
// Define um cookie com o token de autenticação
cookieStore.set('access_token', response.data.token, {
httpOnly: true, // Impede acesso via JavaScript (segurança contra XSS)
secure: process.env.NODE_ENV === 'production', // Garante HTTPS em produção
sameSite: 'strict', // Restringe envio de cookies entre domínios
path: '/', // Torna o cookie acessível em toda a aplicação
maxAge: 60 * 60 * 24, // Define validade de 24 horas (em segundos)
});
// Redireciona o usuário autenticado para a página administrativa de usuários
redirect('/administrativo/usuarios');
}

View file

@ -0,0 +1,36 @@
// src/shared/actions/auth/GetAuthenticatedUser.ts
import { cookies } from 'next/headers';
import jwtDecode from 'jwt-decode';
import GetSigla from '@/shared/actions/text/GetSigla';
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
interface JwtPayload {
id: string;
iat: number;
exp: number;
data?: UserAuthenticatedInterface;
}
export async function getUserFromServer(): Promise<JwtPayload | null> {
try {
const cookieStore = cookies();
const token = cookieStore.get('access_token')?.value;
if (!token) return null;
const decoded: JwtPayload = jwtDecode(token);
if (decoded.data && typeof decoded.data === 'string') {
decoded.data = JSON.parse(decoded.data);
}
if (decoded.data) {
decoded.data.sigla = GetSigla(decoded.data.nome || '');
}
return decoded;
} catch (error) {
console.error('Erro ao decodificar token no server:', error);
return null;
}
}

View file

@ -3,16 +3,17 @@
import { useEffect, useState } from 'react';
import { jwtDecode } from 'jwt-decode';
import GetSigla from '@/shared/actions/text/GetSigla';
import GUsuarioAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
interface JwtPayload {
id: string;
iat: number;
exp: number;
data?: GUsuarioAuthenticatedInterface;
data?: UserAuthenticatedInterface;
}
export default function useGUsuarioGetJWTHook() {
export default function useUserGetJWTHook() {
const [userAuthenticated, setUserAuthenticated] = useState<JwtPayload | null>(null);
useEffect(() => {
@ -21,9 +22,11 @@ export default function useGUsuarioGetJWTHook() {
// Lê o token diretamente do cookie (lado do cliente)
const token = document.cookie
.split('; ')
.find(row => row.startsWith('access_token='))
.find(row => row.startsWith('access_token'))
?.split('=')[1];
console.log('Token encontrado:', token);
// Se não houver token, apenas sai (sem erro nem redirecionamento)
if (!token) {
console.warn('Token ausente ou inválido no cookie.');

View file

@ -0,0 +1,35 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import UserAuthenticatedInterface from '@/shared/interfaces/UserAuthenticatedInterface';
interface JwtPayload {
id: string;
iat: number;
exp: number;
data?: UserAuthenticatedInterface;
}
export default function useUserGetJWTHook() {
const [userAuthenticated, setUserAuthenticated] = useState<JwtPayload | null>(null);
const router = useRouter();
useEffect(() => {
async function fetchUser() {
try {
const res = await fetch('/api/auth/me', { cache: 'no-store' });
if (!res.ok) throw new Error('Erro ao buscar usuário');
const data: JwtPayload | null = await res.json();
setUserAuthenticated(data);
} catch (err) {
console.error('Erro ao buscar usuário logado:', err);
}
}
fetchUser();
}, []);
return { userAuthenticated };
}