diff --git a/package-lock.json b/package-lock.json index 73cb6d5..41066c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "app", - "version": "0.1.0", + "version": "25.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "app", - "version": "0.1.0", + "version": "25.9.1", "dependencies": { "@faker-js/faker": "^10.0.0", "@hookform/resolvers": "^5.2.1", @@ -26,7 +26,9 @@ "clsx": "^2.1.1", "cookies-next": "^6.1.0", "faker-js": "^1.0.0", + "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "lucide-react": "^0.540.0", "next": "^15.5.3", "next-themes": "^0.4.6", @@ -39,6 +41,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20", "@types/react": "^19", @@ -1949,6 +1952,13 @@ "tailwindcss": "4.1.12" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -2224,6 +2234,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -2267,6 +2286,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", diff --git a/package.json b/package.json index 88fd872..8e3e0f5 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "app", - "version": "0.1.0", + "version": "25.9.1", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint" @@ -27,7 +27,9 @@ "clsx": "^2.1.1", "cookies-next": "^6.1.0", "faker-js": "^1.0.0", + "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "lucide-react": "^0.540.0", "next": "^15.5.3", "next-themes": "^0.4.6", @@ -40,6 +42,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^20", "@types/react": "^19", diff --git a/public/images/logo.svg b/public/images/logo.svg new file mode 100644 index 0000000..6199398 --- /dev/null +++ b/public/images/logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/actions/cookies/CookiesGet.ts b/src/actions/cookies/CookiesGet.ts index 238470d..194669f 100644 --- a/src/actions/cookies/CookiesGet.ts +++ b/src/actions/cookies/CookiesGet.ts @@ -1,11 +1,19 @@ -'use client' +'use server' import { cookies } from "next/headers"; -export default async function CookiesGet() { +/** + * Função para obter tokens do lado servidorde acordo com o nome informado +*/ +export default async function CookiesGet(token: string) { + // Obtem a lista de Tokens const cookieStore = await cookies(); - const token = cookieStore.get('access_token'); - return token?.value; + + // Obtem um token em especifico + const data = cookieStore.get(token); + + // Retorna nulo ou o valor do token desejado + return data?.value; } \ No newline at end of file diff --git a/src/actions/text/GetSigla.ts b/src/actions/text/GetSigla.ts new file mode 100644 index 0000000..f6550ae --- /dev/null +++ b/src/actions/text/GetSigla.ts @@ -0,0 +1,15 @@ +export default function GetSigla(data: string): string { + + if (!data) return ""; + + // Remove espaços extras no início e no fim e divide em palavras + const palavras = data.trim().split(/\s+/); + + if (palavras.length === 1) { + // Apenas uma palavra → retorna as duas primeiras letras + return palavras[0].substring(0, 2).toUpperCase(); + } + + // Duas ou mais palavras → retorna a primeira letra de cada + return palavras.map(palavra => palavra.charAt(0).toUpperCase()).join(''); +} \ No newline at end of file diff --git a/src/app/(protected)/(administrativo)/_hooks/g_usuario/useGUsuarioLogoutHook.ts b/src/app/(protected)/(administrativo)/_hooks/g_usuario/useGUsuarioLogoutHook.ts new file mode 100644 index 0000000..7220189 --- /dev/null +++ b/src/app/(protected)/(administrativo)/_hooks/g_usuario/useGUsuarioLogoutHook.ts @@ -0,0 +1,14 @@ +'use client' + +import GUsuarioLogoutService from "../../_services/g_usuario/GUsuarioLogoutService"; + +export const useGUsuarioLogoutHook = () => { + + const logoutUsuario = async () => { + + await GUsuarioLogoutService('access_token') + + } + + return { logoutUsuario } +} \ No newline at end of file diff --git a/src/app/(protected)/(administrativo)/_services/g_usuario/GUsuarioLogoutService.ts b/src/app/(protected)/(administrativo)/_services/g_usuario/GUsuarioLogoutService.ts new file mode 100644 index 0000000..4747715 --- /dev/null +++ b/src/app/(protected)/(administrativo)/_services/g_usuario/GUsuarioLogoutService.ts @@ -0,0 +1,19 @@ +'use server' + +import { + cookies +} from "next/headers"; + +import { redirect } from "next/navigation"; + +export default async function GUsuarioLogoutService(token: string) { + + const cookieStore = await cookies(); + cookieStore.set(token, '', { + expires: new Date(0), + path: '/', + }); + + redirect('/login'); + +} \ No newline at end of file diff --git a/src/app/(protected)/layout.tsx b/src/app/(protected)/layout.tsx index 1ab8b42..982240e 100644 --- a/src/app/(protected)/layout.tsx +++ b/src/app/(protected)/layout.tsx @@ -49,7 +49,7 @@ export default function RootLayout({ -
+
) { + + const { userAuthenticated } = useGUsuarioGetJWTHook(); + return ( + + - + + + + + + + + + +
+ + + +
+ +
+ + + + Orius Tecnologia + + + + + + 25.9.1 + + + +
+ +
+ +
+ +
+ +
+
+ + + + - + + + + +
+ ) } diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx index c7d45b5..a622c89 100644 --- a/src/components/nav-main.tsx +++ b/src/components/nav-main.tsx @@ -34,41 +34,85 @@ export function NavMain({ }[] }) { return ( + - Platform + + + + Platform + + + + {items.map((item) => ( + + + + + {item.icon && } - {item.title} + + + + {item.title} + + + + + + + + {item.items?.map((subItem) => ( + + + - {subItem.title} + + + + {subItem.title} + + + + + + ))} + + + + + ))} + + + ) } diff --git a/src/components/nav-user.tsx b/src/components/nav-user.tsx index 3d6d9f8..0db31d3 100644 --- a/src/components/nav-user.tsx +++ b/src/components/nav-user.tsx @@ -1,14 +1,10 @@ "use client" import { - BadgeCheck, - Bell, ChevronsUpDown, - CreditCard, LogOut, Sparkles, } from "lucide-react" - import { Avatar, AvatarFallback, @@ -30,85 +26,192 @@ import { useSidebar, } from "@/components/ui/sidebar" +import GUsuarioAuthenticatedInterface from "@/interfaces/GUsuarioAuthenticatedInterface" +import ConfirmDialog from "@/app/_components/confirm_dialog/ConfirmDialog" +import { useGUsuarioLogoutHook } from "@/app/(protected)/(administrativo)/_hooks/g_usuario/useGUsuarioLogoutHook" +import { use, useCallback, useState } from "react" + export function NavUser({ - user, + + user + }: { - user: { - name: string - email: string - avatar: string - } + + user: GUsuarioAuthenticatedInterface + }) { - const { isMobile } = useSidebar() + + // Hook para encerrar sessão + const { logoutUsuario } = useGUsuarioLogoutHook(); + + // Controle de exibição do formulário de confirmação + const [isConfirmOpen, setIsConfirmOpen] = useState(false); + + const { isMobile } = useSidebar(); + + // Manipulação de formulário de confirmação + const handleConfirmOpen = useCallback(async () => { + + setIsConfirmOpen(true); + + }, []); + + const handleLogoutConfirm = useCallback(async () => { + + logoutUsuario(); + + }, []); + + const handleLogoutCancel = useCallback(async () => { + + setIsConfirmOpen(false); + + }, []) + + if (!user) { + + return 'Carregando...'; + + } return ( - - - - - - - - CN - -
- {user.name} - {user.email} -
- -
-
- - -
+ + <> + + + + + + + + + + - - CN + + + + + + {user.sigla} + + + +
- {user.name} - {user.email} + + + + {user.nome} + + + + + + {user.email} + + +
-
-
- - - - - Upgrade to Pro + + + + + + + + + + + +
+ + + + + + + + {user.sigla} + + + + + +
+ + + + {user.nome} + + + + + + {user.email} + + + +
+ +
+ +
+ + + + + + + + + + Configurações + + + + + + + + handleConfirmOpen()}> + + + + Log out + -
- - - - - Account - - - - Billing - - - - Notifications - - - - - - Log out - -
-
-
-
+ + + + + + + + + + {/* Modal de confirmação */} + handleLogoutConfirm()} + onCancel={() => handleLogoutCancel()} + /> + + + ) } diff --git a/src/hooks/auth/useGUsuarioGetJWTHook.ts b/src/hooks/auth/useGUsuarioGetJWTHook.ts new file mode 100644 index 0000000..02d1571 --- /dev/null +++ b/src/hooks/auth/useGUsuarioGetJWTHook.ts @@ -0,0 +1,67 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { jwtDecode } from "jwt-decode"; +import CookiesGet from "../../actions/cookies/CookiesGet"; +import GetSigla from "@/actions/text/GetSigla"; + +interface JwtPayload { + id: string; + iat: number; + exp: number; + data?: string; +} + +export default function useGUsuarioGetJWTHook() { + + const [userAuthenticated, setUserAuthenticated] = useState(null); + + useEffect(() => { + + async function fetchToken() { + + try { + + // Executa a serve action para obtem o token, salvo em cookies + const token = await CookiesGet("access_token"); + + // Verifica se o token esta preenchido + if (!token) { + + console.error("Não foi localizado dados dentro do token"); + + // Encerra a aplicação + return; + } + + // Decodifica os dados do JWT + const decoded = jwtDecode(token); + + // Se existir campo data e for string, corrige aspas simples + if (decoded.data && typeof decoded.data === "string") { + + // Decodifica os dados enviado via json + decoded.data = JSON.parse(decoded.data); + + // Gera Sigla para o nome + decoded.data.sigla = GetSigla(decoded.data.nome) + + } + + // Armazena os dados decodificados + setUserAuthenticated(decoded); + + } catch (error) { + + console.error("Erro ao buscar token", error); + + } + } + + // Busca o TOken + fetchToken(); + + }, []); + + return { userAuthenticated }; +} diff --git a/src/interfaces/GUsuarioAuthenticatedInterface.ts b/src/interfaces/GUsuarioAuthenticatedInterface.ts new file mode 100644 index 0000000..da1f8f3 --- /dev/null +++ b/src/interfaces/GUsuarioAuthenticatedInterface.ts @@ -0,0 +1,13 @@ +export default interface GUsuarioAuthenticatedInterface { + usuario_id?: number, + login?: string, + situacao?: string, + nome_completo?: string, + funcao?: string, + assina?: string, + sigla?: string, + email?: string, + assina_certidao?: string, + foto?: string, + cpf?: string, +} \ No newline at end of file