diff --git a/src/hooks/useAdmin.ts b/src/hooks/useAdmin.ts new file mode 100644 index 0000000..d364ba2 --- /dev/null +++ b/src/hooks/useAdmin.ts @@ -0,0 +1,44 @@ +import { useState, useEffect } from 'react'; +import { adminLogin } from '@/lib/pocketbase'; + +export function useAdmin() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let mounted = true; + + const initializeAdmin = async () => { + try { + setLoading(true); + await adminLogin(); + if (mounted) { + setIsLoggedIn(true); + setError(null); + } + } catch (err) { + if (mounted) { + setError(err instanceof Error ? err.message : 'Failed to login as admin'); + setIsLoggedIn(false); + } + } finally { + if (mounted) { + setLoading(false); + } + } + }; + + initializeAdmin(); + + return () => { + mounted = false; + }; + }, []); // Empty dependency array ensures this only runs once on mount + + return { + isLoggedIn, + loading, + error + }; +} \ No newline at end of file diff --git a/src/hooks/useDataService.ts b/src/hooks/useDataService.ts index 8e677ef..a132ee5 100644 --- a/src/hooks/useDataService.ts +++ b/src/hooks/useDataService.ts @@ -7,7 +7,6 @@ export function useJobs() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // Load jobs only once when the hook mounts useEffect(() => { let mounted = true; @@ -30,7 +29,6 @@ export function useJobs() { } }; - // Start loading immediately loadJobs(); // Set up subscription for updates diff --git a/src/lib/pocketbase.ts b/src/lib/pocketbase.ts index 2ad5979..c6ace76 100644 --- a/src/lib/pocketbase.ts +++ b/src/lib/pocketbase.ts @@ -3,13 +3,67 @@ import { TypedPocketBase } from './pbtypes'; import { POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD } from './pocketbaseAdmin'; const pb = new PocketBase('https://evebase.site.quack-lab.dev') as TypedPocketBase; + +// Track the login promise to avoid multiple concurrent login attempts +let loginPromise: Promise | null = null; + async function adminLogin() { - try { - await pb.collection('_superusers').authWithPassword(POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD); - console.log('Admin logged in'); - } catch (error) { - console.error('Admin login failed:', error); + // If we're already logged in, no need to login again + if (pb.authStore.isValid) { + return Promise.resolve(); } + + if (loginPromise) { + return loginPromise; + } + + loginPromise = pb.collection('_superusers') + .authWithPassword(POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD) + .then(() => { + console.log('Admin logged in'); + }) + .catch(error => { + console.error('Admin login failed:', error); + throw error; + }) + .finally(() => { + loginPromise = null; + }); + + return loginPromise; } -export { pb, adminLogin }; +// Wrap the PocketBase instance to ensure auth before any request +const wrappedPb = new Proxy(pb, { + get(target, prop) { + if (prop === 'collection') { + // Return a function that creates a proxied collection + return (collectionName: string) => { + // Get the original collection + const originalCollection = target.collection(collectionName); + + // Return a proxy for the collection that ensures auth + return new Proxy(originalCollection, { + get(collectionTarget, methodName) { + const method = collectionTarget[methodName as keyof typeof collectionTarget]; + if (typeof method === 'function') { + // Wrap methods to ensure auth before calling + return async (...args: any[]) => { + // Only try to login if we're not already logged in + if (!pb.authStore.isValid) { + await adminLogin(); + } + return method.apply(collectionTarget, args); + }; + } + return method; + } + }); + }; + } + + return target[prop as keyof typeof target]; + } +}); + +export { wrappedPb as pb, adminLogin }; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 4c0c2e3..fec09b1 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -7,7 +7,6 @@ import { formatISK } from '@/utils/priceUtils'; import JobCard from '@/components/JobCard'; import JobForm from '@/components/JobForm'; import { IndJob } from '@/lib/types'; -import { Badge } from '@/components/ui/badge'; import BatchTransactionForm from '@/components/BatchTransactionForm'; import { useJobs } from '@/hooks/useDataService'; import SearchOverlay from '@/components/SearchOverlay'; diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 8246481..4650420 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -3,16 +3,24 @@ import { IndJobRecord, IndJobRecordNoId, IndTransactionRecord, IndTransactionRec import * as jobService from './jobService'; import * as transactionService from './transactionService'; import * as billItemService from './billItemService'; +import { adminLogin } from '@/lib/pocketbase'; export class DataService { private static instance: DataService; private jobs: IndJob[] = []; private listeners: Set<() => void> = new Set(); private loadPromise: Promise | null = null; + private initialized: Promise; - private constructor() { } + private constructor() { + // Initialize with admin login + this.initialized = adminLogin().catch(error => { + console.error('Failed to initialize DataService:', error); + throw error; + }); + } - static getInstance(): DataService { + public static getInstance(): DataService { if (!DataService.instance) { DataService.instance = new DataService(); } @@ -37,6 +45,9 @@ export class DataService { } async loadJobs(): Promise { + // Wait for initialization first + await this.initialized; + // If there's already a load in progress, return that promise if (this.loadPromise) { return this.loadPromise;