fix: resolve seeder connection, backoffice scroll and auth session refresh
This commit is contained in:
parent
928997c9ce
commit
621e4594c6
6 changed files with 94 additions and 24 deletions
|
|
@ -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() {
|
|||
<h1 className="text-3xl font-bold text-foreground">Backoffice</h1>
|
||||
<p className="text-muted-foreground mt-1">Controle administrativo do GoHorse Jobs</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={loadBackoffice} className="gap-2">
|
||||
<Button variant="outline" onClick={() => loadBackoffice(false)} className="gap-2">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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,6 +38,7 @@ export default function RootLayout({
|
|||
<ConfigProvider>
|
||||
<I18nProvider>
|
||||
<ThemeProvider>
|
||||
<AuthProvider>
|
||||
<NotificationProvider>
|
||||
<Suspense fallback={<LoadingScreen text="GoHorse Jobs" />}>{children}</Suspense>
|
||||
<Toaster
|
||||
|
|
@ -47,6 +49,7 @@ export default function RootLayout({
|
|||
duration={4000}
|
||||
/>
|
||||
</NotificationProvider>
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</I18nProvider>
|
||||
</ConfigProvider>
|
||||
|
|
|
|||
58
frontend/src/contexts/AuthContext.tsx
Normal file
58
frontend/src/contexts/AuthContext.tsx
Normal file
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
user: null,
|
||||
loading: true,
|
||||
checkSession: async () => { },
|
||||
});
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(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 (
|
||||
<AuthContext.Provider value={{ user, loading, checkSession }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useAuth = () => useContext(AuthContext);
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue