From 38a76c2076cb6c3d1c54e56bb6f6da7d1bee176f Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Fri, 6 Mar 2026 11:13:43 -0600 Subject: [PATCH] chore: organize documentation, fix infinite loops in hooks, and sync package.json --- .../STATUS_REPORT_SAVEINMED.md | 0 frontend/package-lock.json | 4 +- frontend/package.json | 73 ++++++++----------- frontend/src/components/ProtectedRoute.tsx | 20 ++++- frontend/src/context/AuthContext.tsx | 48 ++++++++++-- frontend/src/hooks/useUsuariosApi.ts | 2 +- frontend/src/pages/auth/Login.tsx | 6 +- 7 files changed, 101 insertions(+), 52 deletions(-) rename STATUS_REPORT_SAVEINMED.md => docs/STATUS_REPORT_SAVEINMED.md (100%) diff --git a/STATUS_REPORT_SAVEINMED.md b/docs/STATUS_REPORT_SAVEINMED.md similarity index 100% rename from STATUS_REPORT_SAVEINMED.md rename to docs/STATUS_REPORT_SAVEINMED.md diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c1d9c88..9e49bfc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "frontend", + "name": "marketplace-front", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "frontend", + "name": "marketplace-front", "version": "0.1.0", "dependencies": { "@heroicons/react": "^2.2.0", diff --git a/frontend/package.json b/frontend/package.json index 800b50c..07a8935 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,51 +1,42 @@ { "name": "marketplace-front", + "version": "0.1.0", "private": true, - "version": "0.0.0", - "type": "module", "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "preview": "vite preview", - "test": "vitest", - "test:run": "vitest run", - "test:coverage": "vitest run --coverage", - "e2e": "playwright test", - "e2e:ui": "playwright test --ui", - "e2e:headed": "playwright test --headed", - "e2e:debug": "playwright test --debug" + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" }, "dependencies": { + "@heroicons/react": "^2.2.0", "@mercadopago/sdk-react": "^1.0.6", - "@types/leaflet": "^1.9.21", - "axios": "^1.7.4", - "leaflet": "^1.9.4", - "lucide-react": "^0.562.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-icons": "^5.5.0", - "react-leaflet": "^4.2.1", - "react-router-dom": "^6.26.0", - "react-window": "^1.8.10", - "zustand": "^4.5.5" + "@react-email/render": "^1.4.0", + "@types/nodemailer": "^7.0.3", + "appwrite": "^18.1.1", + "axios": "^1.12.2", + "chart.js": "^4.5.0", + "dotenv": "^17.2.2", + "lucide-react": "^0.539.0", + "next": "15.3.4", + "node-appwrite": "^17.2.0", + "nodemailer": "^7.0.10", + "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", + "react-dom": "^19.0.0", + "react-hot-toast": "^2.6.0", + "resend": "^6.4.2" }, "devDependencies": { - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.1", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.0.0", - "@types/react": "^18.3.7", - "@types/react-dom": "^18.3.0", - "@types/react-window": "^1.8.8", - "@vitejs/plugin-react": "^4.7.0", - "@vitest/coverage-v8": "^4.0.16", - "autoprefixer": "^10.4.20", - "jsdom": "^27.3.0", - "postcss": "^8.4.47", - "tailwindcss": "^3.4.10", - "typescript": "^5.6.2", - "vite": "^5.4.3", - "vitest": "^4.0.16", - "@playwright/test": "^1.49.0" + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-chartjs-2": "^2.0.2", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.3.4", + "tailwindcss": "^4", + "typescript": "^5" } -} +} \ No newline at end of file diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute.tsx index 583c588..2ef736e 100644 --- a/frontend/src/components/ProtectedRoute.tsx +++ b/frontend/src/components/ProtectedRoute.tsx @@ -7,11 +7,27 @@ interface ProtectedRouteProps { allowedRoles?: UserRole[] } +const getFallbackRoute = (role?: UserRole) => { + switch (role) { + case 'admin': + return '/dashboard' + case 'owner': + case 'seller': + return '/seller' + case 'employee': + return '/colaborador' + case 'delivery': + return '/entregas' + default: + return '/login' + } +} + export function ProtectedRoute({ children, allowedRoles }: ProtectedRouteProps) { const { user, loading } = useAuth() if (loading) { - return
Carregando sessão...
+ return
Carregando sessao...
} if (!user) { @@ -19,7 +35,7 @@ export function ProtectedRoute({ children, allowedRoles }: ProtectedRouteProps) } if (allowedRoles && !allowedRoles.includes(user.role)) { - return + return } return <>{children} diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 5536631..80c63d2 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -2,6 +2,7 @@ import { createContext, ReactNode, useContext, useEffect, useMemo, useState } fr import { useNavigate } from 'react-router-dom' import { apiClient } from '../services/apiClient' import { authService } from '../services/auth' +import { decodeJwtPayload } from '../utils/jwt' export type UserRole = 'admin' | 'owner' | 'employee' | 'delivery' | 'seller' | 'customer' @@ -27,14 +28,49 @@ const AuthContext = createContext(undefined) const AUTH_KEY = 'mp-auth-user' +const normalizeRole = (role?: string): UserRole => { + const normalizedRole = role?.toLowerCase() + + if (normalizedRole?.includes('admin')) { + return 'admin' + } + + switch (normalizedRole) { + case 'admin': + return 'admin' + case 'owner': + case 'dono': + return 'owner' + case 'employee': + case 'colaborador': + return 'employee' + case 'delivery': + case 'entregador': + return 'delivery' + case 'customer': + return 'customer' + case 'seller': + default: + return 'seller' + } +} + export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(() => { const persisted = localStorage.getItem(AUTH_KEY) const parsed = persisted ? (JSON.parse(persisted) as AuthUser) : null - if (parsed?.token) { - apiClient.setToken(parsed.token) + const payload = decodeJwtPayload<{ role?: string; company_id?: string }>(parsed?.token) + const normalized = parsed + ? { + ...parsed, + role: normalizeRole(payload?.role ?? parsed.role), + companyId: parsed.companyId ?? payload?.company_id, + } + : null + if (normalized?.token) { + apiClient.setToken(normalized.token) } - return parsed + return normalized }) const [loading, setLoading] = useState(true) const navigate = useNavigate() @@ -54,10 +90,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, [user]) const login = (token: string, role: UserRole, name: string, id: string, companyId?: string, email?: string, username?: string) => { - setUser({ token, role, name, id, companyId, email, username }) + const payload = decodeJwtPayload<{ role?: string; company_id?: string }>(token) + const normalizedRole = normalizeRole(payload?.role ?? role) + setUser({ token, role: normalizedRole, name, id, companyId: companyId ?? payload?.company_id, email, username }) // Redirect based on role - switch (role) { + switch (normalizedRole) { case 'admin': navigate('/dashboard', { replace: true }) break diff --git a/frontend/src/hooks/useUsuariosApi.ts b/frontend/src/hooks/useUsuariosApi.ts index cfecfc5..7ce2d9d 100644 --- a/frontend/src/hooks/useUsuariosApi.ts +++ b/frontend/src/hooks/useUsuariosApi.ts @@ -58,7 +58,7 @@ export const useUsuariosApi = (): UseUsuariosApiReturn => { } finally { setLoading(false); } - }, [currentPage]); + }, []); const buscarUsuarioPorId = useCallback(async (id: string): Promise => { try { diff --git a/frontend/src/pages/auth/Login.tsx b/frontend/src/pages/auth/Login.tsx index 324af35..2421310 100644 --- a/frontend/src/pages/auth/Login.tsx +++ b/frontend/src/pages/auth/Login.tsx @@ -52,7 +52,11 @@ export function LoginPage() { const resolveRole = (role?: string): UserRole => { logger.info('🔐 [Login] Resolving role:', role) - switch (role?.toLowerCase()) { + const normalizedRole = role?.toLowerCase() + + if (normalizedRole?.includes('admin')) return 'admin' + + switch (normalizedRole) { case 'admin': return 'admin' case 'dono': return 'owner' case 'colaborador': return 'employee'