From 621e4594c6ee8ddb017321f3bdf85f51d80b8da5 Mon Sep 17 00:00:00 2001 From: Yamamoto Date: Sat, 3 Jan 2026 12:11:38 -0300 Subject: [PATCH] fix: resolve seeder connection, backoffice scroll and auth session refresh --- .../src/app/dashboard/backoffice/page.tsx | 20 +++---- frontend/src/app/layout.tsx | 23 ++++---- frontend/src/contexts/AuthContext.tsx | 58 +++++++++++++++++++ frontend/src/lib/auth.ts | 2 +- seeder-api/src/index.js | 6 +- seeder-api/src/seeders/location-loader.js | 9 ++- 6 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 frontend/src/contexts/AuthContext.tsx diff --git a/frontend/src/app/dashboard/backoffice/page.tsx b/frontend/src/app/dashboard/backoffice/page.tsx index 9bf30a1..1167808 100644 --- a/frontend/src/app/dashboard/backoffice/page.tsx +++ b/frontend/src/app/dashboard/backoffice/page.tsx @@ -68,9 +68,9 @@ export default function BackofficePage() { loadBackoffice() }, [router]) - const loadBackoffice = async () => { + const loadBackoffice = async (silent = false) => { try { - setLoading(true) + if (!silent) setLoading(true) const [rolesData, auditData, companiesData, jobsData, tagsData] = await Promise.all([ adminAccessApi.listRoles(), adminAuditApi.listLogins(20), @@ -87,7 +87,7 @@ export default function BackofficePage() { console.error("Error loading backoffice:", error) toast.error("Failed to load backoffice data") } finally { - setLoading(false) + if (!silent) setLoading(false) } } @@ -95,7 +95,7 @@ export default function BackofficePage() { try { await adminCompaniesApi.updateStatus(companyId, { verified: true }) toast.success("Company approved") - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error approving company:", error) toast.error("Failed to approve company") @@ -106,7 +106,7 @@ export default function BackofficePage() { try { await adminCompaniesApi.updateStatus(companyId, { active: false }) toast.success("Company deactivated") - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error deactivating company:", error) toast.error("Failed to deactivate company") @@ -117,7 +117,7 @@ export default function BackofficePage() { try { await adminJobsApi.updateStatus(jobId, status) toast.success("Job status updated") - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error updating job status:", error) toast.error("Failed to update job status") @@ -128,7 +128,7 @@ export default function BackofficePage() { try { await adminJobsApi.duplicate(jobId) toast.success("Job duplicated as draft") - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error duplicating job:", error) toast.error("Failed to duplicate job") @@ -145,7 +145,7 @@ export default function BackofficePage() { await adminTagsApi.create({ name: tagForm.name.trim(), category: tagForm.category }) toast.success("Tag created") setTagForm({ name: "", category: "area" }) - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error creating tag:", error) toast.error("Failed to create tag") @@ -158,7 +158,7 @@ export default function BackofficePage() { try { await adminTagsApi.update(tag.id, { active: !tag.active }) toast.success("Tag updated") - loadBackoffice() + loadBackoffice(true) } catch (error) { console.error("Error updating tag:", error) toast.error("Failed to update tag") @@ -180,7 +180,7 @@ export default function BackofficePage() {

Backoffice

Controle administrativo do GoHorse Jobs

- diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 7ef37c4..dd33e6f 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -11,6 +11,7 @@ import { I18nProvider } from "@/lib/i18n" import "./globals.css" import { Suspense } from "react" import { LoadingScreen } from "@/components/ui/loading-spinner" +import { AuthProvider } from "@/contexts/AuthContext" export const metadata: Metadata = { title: "GoHorseJobs - Find your next opportunity", @@ -37,16 +38,18 @@ export default function RootLayout({ - - }>{children} - - + + + }>{children} + + + diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..5adc82e --- /dev/null +++ b/frontend/src/contexts/AuthContext.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { createContext, useContext, useEffect, useState } from "react"; +import { refreshSession, User, getCurrentUser } from "@/lib/auth"; + +interface AuthContextType { + user: User | null; + loading: boolean; + checkSession: () => Promise; +} + +const AuthContext = createContext({ + user: null, + loading: true, + checkSession: async () => { }, +}); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + const checkSession = async () => { + try { + // First check local storage for immediate feedback (optimistic) + const stored = getCurrentUser(); + if (stored) { + setUser(stored); + } + + // Then verify with backend (httpOnly cookie) + const refreshedUser = await refreshSession(); + if (refreshedUser) { + setUser(refreshedUser); + } else { + // If backend fails but we had local storage, it means session invalid/expired + // refreshSession already clears localStorage in that case + setUser(null); + } + } catch (error) { + console.error("Auth initialization failed", error); + setUser(null); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + checkSession(); + }, []); + + return ( + + {children} + + ); +} + +export const useAuth = () => useContext(AuthContext); diff --git a/frontend/src/lib/auth.ts b/frontend/src/lib/auth.ts index ee7a300..0e2a1af 100644 --- a/frontend/src/lib/auth.ts +++ b/frontend/src/lib/auth.ts @@ -96,7 +96,7 @@ export function getCurrentUser(): User | null { const stored = localStorage.getItem(AUTH_KEY); if (stored) { const user = JSON.parse(stored); - console.log("%c[AUTH] User Loaded from Storage", "color: #10b981", user.email); + // console.log("%c[AUTH] User Loaded from Storage", "color: #10b981", user.email); return user; } // User not in storage (normal state) diff --git a/seeder-api/src/index.js b/seeder-api/src/index.js index 3a30704..4807e1d 100644 --- a/seeder-api/src/index.js +++ b/seeder-api/src/index.js @@ -119,9 +119,9 @@ async function seedDatabase() { } catch (error) { console.error('\nāŒ Seeding failed:', error.message); console.error(error); - } finally { - await closePool(); + throw error; } + // Do NOT close pool here } // Lite version (skips cities for faster seeding) @@ -228,6 +228,8 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) { } catch (error) { console.error('āŒ Execution failed:', error); process.exit(1); + } finally { + await import('./db.js').then(m => m.closePool()); } })(); } diff --git a/seeder-api/src/seeders/location-loader.js b/seeder-api/src/seeders/location-loader.js index 0512cfd..30efab5 100644 --- a/seeder-api/src/seeders/location-loader.js +++ b/seeder-api/src/seeders/location-loader.js @@ -327,6 +327,13 @@ async function executeSqlFile(filename, tableName) { pgStmt = transformStatesInsert(pgStmt); } + // prevent duplicate key errors + if (pgStmt.trim().endsWith(';')) { + pgStmt = pgStmt.trim().slice(0, -1) + ' ON CONFLICT DO NOTHING;'; + } else { + pgStmt += ' ON CONFLICT DO NOTHING;'; + } + await pool.query(pgStmt); } @@ -436,7 +443,7 @@ async function executeGzippedSqlFile(filename, tableName) { for (let i = 0; i < valueBatches.length; i++) { const batch = valueBatches[i]; - const query = `INSERT INTO ${tableName} ${columns} VALUES ${batch.join(', ')}`; + const query = `INSERT INTO ${tableName} ${columns} VALUES ${batch.join(', ')} ON CONFLICT DO NOTHING`; await pool.query(query); if ((i + 1) % 10 === 0 || i === valueBatches.length - 1) {