diff --git a/package-lock.json b/package-lock.json index 18d5b38..e19e3dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1097,7 +1096,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1174,7 +1172,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1785,7 +1782,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1797,7 +1793,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -1953,8 +1948,7 @@ "version": "0.182.0", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tr46": { "version": "0.0.3", @@ -2002,7 +1996,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 1ef1fc8..cbdd777 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -1,24 +1,61 @@ import { createContext, useContext, useState, useEffect } from 'react' -import { account, databases, DATABASE_ID, COLLECTIONS, ID, Query } from '../lib/appwrite' +import { account, databases, DATABASE_ID, COLLECTIONS, ID, Query, hasAppwriteSession } from '../lib/appwrite' const AuthContext = createContext() // Demo mode when Appwrite is not configured -const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID +const DEMO_MODE = !(import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim() + +const PROJECT_ID = (import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim() + +function clearStaleAppwriteSessions() { + if (typeof window === 'undefined' || !window.localStorage) return + try { + const raw = window.localStorage.getItem('cookieFallback') + if (!raw) return + const parsed = JSON.parse(raw) + if (typeof parsed !== 'object' || parsed === null) return + for (const key of Object.keys(parsed)) { + if (key.startsWith('a_session_') && key !== `a_session_${PROJECT_ID}`) { + delete parsed[key] + } + } + window.localStorage.setItem('cookieFallback', JSON.stringify(parsed)) + } catch { + window.localStorage.removeItem('cookieFallback') + } +} + +function clearAllLocalAppwriteSessions() { + if (typeof window === 'undefined') return + try { + window.localStorage.removeItem('cookieFallback') + } catch { /* ignore */ } +} + +async function createSessionWithCleanup(email, password) { + try { + await account.deleteSessions() + } catch { /* alte Sitzung ggf. nur serverseitig */ } + clearAllLocalAppwriteSessions() + if (account.createEmailPasswordSession) { + await account.createEmailPasswordSession(email, password) + } else { + await account.createEmailSession(email, password) + } +} // Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu async function ensureEmployeeExists(user) { if (!user || DEMO_MODE) return - + try { - // Prüfe ob User bereits in employees Collection existiert const response = await databases.listDocuments( DATABASE_ID, COLLECTIONS.EMPLOYEES, [Query.equal('userId', user.$id)] ) - - // Wenn User noch nicht existiert, füge ihn hinzu + if (response.documents.length === 0) { await databases.createDocument( DATABASE_ID, @@ -28,13 +65,12 @@ async function ensureEmployeeExists(user) { userId: user.$id, displayName: user.name || user.email, email: user.email, - shortcode: '' // Kürzel wird später vom Admin hinzugefügt + shortcode: '' } ) console.log('✅ User automatisch zur Mitarbeiter-Liste hinzugefügt') } } catch (error) { - // Fehler ignorieren wenn Collection nicht existiert oder Permissions fehlen if (error.code !== 404) { console.warn('Could not add user to employees collection:', error.message) } @@ -51,7 +87,6 @@ export function AuthProvider({ children }) { async function checkUser() { if (DEMO_MODE) { - // Check localStorage for demo session const demoUser = localStorage.getItem('demo_user') if (demoUser) { setUser(JSON.parse(demoUser)) @@ -59,18 +94,24 @@ export function AuthProvider({ children }) { setLoading(false) return } - + + if (!hasAppwriteSession()) { + setUser(null) + setLoading(false) + return + } + try { const session = await account.get() setUser(session) - // Automatisch zur employees Collection hinzufügen await ensureEmployeeExists(session) } catch (error) { - // Kein Fehler loggen beim initialen Check - das ist normal wenn nicht eingeloggt - // Nur loggen wenn es ein unerwarteter Fehler ist (nicht 401) if (error.code !== 401 && error.code !== 404) { console.error('Unexpected error checking user:', error) } + if (error.code === 401) { + clearAllLocalAppwriteSessions() + } setUser(null) } finally { setLoading(false) @@ -79,45 +120,45 @@ export function AuthProvider({ children }) { async function login(email, password) { if (DEMO_MODE) { - // Demo login - accept any credentials const demoUser = { $id: 'demo', email, name: email.split('@')[0] } localStorage.setItem('demo_user', JSON.stringify(demoUser)) setUser(demoUser) return { success: true } } - + try { - // Appwrite 1.5.7 / SDK 13.0 - versuche beide Methoden für Kompatibilität - try { - await account.createEmailSession(email, password) - } catch (e) { - // Fallback für ältere API - if (account.createEmailPasswordSession) { - await account.createEmailPasswordSession(email, password) - } else { - throw e - } - } - - // User-Daten laden und automatisch zur employees Collection hinzufügen + const normalizedEmail = email.trim() + const normalizedPassword = password.trim() + clearStaleAppwriteSessions() + + await createSessionWithCleanup(normalizedEmail, normalizedPassword) + const session = await account.get() setUser(session) await ensureEmployeeExists(session) - + return { success: true } } catch (error) { console.error('Login error:', error) let errorMessage = error.message || 'Login fehlgeschlagen' - - // Bessere Fehlermeldungen - if (error.code === 401 || errorMessage.includes('Invalid credentials')) { + + if (error.code === 429 || errorMessage.includes('Rate limit')) { + errorMessage = 'Zu viele Login-Versuche. Bitte 15–30 Minuten warten und es erneut versuchen.' + } else if (errorMessage.includes('missing scopes') && errorMessage.includes('account')) { + errorMessage = 'Session konnte nicht gespeichert werden. Bitte Seite neu laden und erneut versuchen.' + } else if ( + error?.type === 'user_session_already_exists' || + errorMessage.includes('session is active') + ) { + errorMessage = 'Es gibt noch eine alte Sitzung. Bitte Seite neu laden und erneut anmelden.' + } else if (error.code === 401 || errorMessage.includes('Invalid credentials')) { errorMessage = 'Ungültige Email oder Passwort' } else if (errorMessage.includes('User not found')) { errorMessage = 'Benutzer nicht gefunden. Bitte registriere dich zuerst.' } else if (errorMessage.includes('Email/Password')) { errorMessage = 'Email/Password Authentifizierung ist nicht aktiviert. Bitte aktiviere sie in deinem Appwrite Dashboard unter Auth → Providers.' } - + return { success: false, error: errorMessage } } } @@ -128,28 +169,25 @@ export function AuthProvider({ children }) { setUser(null) return } - + try { - await account.deleteSession('current') - setUser(null) + await account.deleteSessions() } catch (error) { console.error('Logout error:', error) + } finally { + clearAllLocalAppwriteSessions() + setUser(null) } } - // Hilfsfunktion um zu prüfen ob Benutzer Admin ist - const isAdmin = () => { - if (!user) return false - // Prüfe ob Benutzer das "admin" Label hat - return user.labels?.includes('admin') || false - } + const isAdmin = Boolean(user?.labels?.includes('admin')) const value = { user, loading, login, logout, - isAdmin: isAdmin() + isAdmin } return ( diff --git a/src/lib/appwrite.js b/src/lib/appwrite.js index 738d5d1..4c492cb 100644 --- a/src/lib/appwrite.js +++ b/src/lib/appwrite.js @@ -1,6 +1,5 @@ import { Client, Account, Databases, Storage, ID, Query } from 'appwrite' -// Debug: Zeige geladene Umgebungsvariablen (nur in Development) if (import.meta.env.DEV) { console.log('🔧 Appwrite Konfiguration:') console.log('Endpoint:', import.meta.env.VITE_APPWRITE_ENDPOINT || 'NICHT GESETZT') @@ -8,8 +7,8 @@ if (import.meta.env.DEV) { console.log('Database ID:', import.meta.env.VITE_APPWRITE_DATABASE_ID || 'NICHT GESETZT') } -const endpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || 'https://appwrite.webklar.com/v1' -const projectId = import.meta.env.VITE_APPWRITE_PROJECT_ID || '' +const endpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || 'https://ticket.webklar.com/v1' +const projectId = (import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim() if (!projectId) { console.error('❌ FEHLER: VITE_APPWRITE_PROJECT_ID ist nicht gesetzt!') @@ -26,18 +25,35 @@ export const storage = new Storage(client) export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID || 'woms-database' -// Collection IDs - Verwende die tatsächlichen Collection IDs aus Appwrite! export const COLLECTIONS = { - WORKORDERS: '6943bf7d001901baa60c', // Collection ID für workorders - CONFIG: 'config', // Collection ID für Admin-Konfiguration (wird erstellt) - CUSTOMERS: '694bd1fb002b2e583d13', // Collection ID für customers - EMPLOYEES: '695280510031c6c6153b', // Collection ID für employees - WORKSHEETS: '6952dbcf0032a92e1168', // Collection ID für worksheets ✅ + WORKORDERS: 'workorders', + CONFIG: 'config', + CUSTOMERS: 'customers', + EMPLOYEES: 'employees', + WORKSHEETS: 'worksheets', USERS: 'users', ATTACHMENTS: 'attachments' } -export const BUCKET_ID = 'woms-attachments' +export const BUCKET_ID = import.meta.env.VITE_APPWRITE_BUCKET_ID || 'woms-attachments' + +/** Prüft ob vermutlich eine Session existiert (cookieFallback oder Same-Origin-Cookie). */ +export function hasAppwriteSession() { + if (typeof window === 'undefined' || !projectId) return false + try { + const apiHost = new URL(endpoint).hostname + if (apiHost === window.location.hostname) { + return true + } + } catch { /* ignore */ } + try { + const raw = window.localStorage.getItem('cookieFallback') + if (!raw) return false + const parsed = JSON.parse(raw) + return Boolean(parsed?.[`a_session_${projectId}`]) + } catch { + return false + } +} -// Helper functions export { ID, Query }