diff --git a/css/components.css b/css/components.css
index f2cc099..7401f7e 100644
--- a/css/components.css
+++ b/css/components.css
@@ -261,6 +261,17 @@ app-header .btn-create-text {
display: none;
}
+app-header .btn-login,
+app-header .btn-profile {
+ padding: var(--space-sm);
+ border-radius: var(--radius-full);
+}
+
+app-header .btn-login-text,
+app-header .btn-profile-text {
+ display: none;
+}
+
@media (min-width: 480px) {
app-header .btn-create {
padding: var(--space-sm) var(--space-md);
@@ -270,6 +281,17 @@ app-header .btn-create-text {
app-header .btn-create-text {
display: inline;
}
+
+ app-header .btn-login,
+ app-header .btn-profile {
+ padding: var(--space-sm) var(--space-md);
+ border-radius: var(--radius-md);
+ }
+
+ app-header .btn-login-text,
+ app-header .btn-profile-text {
+ display: inline;
+ }
}
/* Footer Component */
diff --git a/js/components/app-header.js b/js/components/app-header.js
index 09ff5b5..da63bcd 100644
--- a/js/components/app-header.js
+++ b/js/components/app-header.js
@@ -1,5 +1,6 @@
import { i18n, t } from '../i18n.js';
import { router } from '../router.js';
+import { auth } from '../services/auth.js';
class AppHeader extends HTMLElement {
constructor() {
@@ -14,11 +15,18 @@ class AppHeader extends HTMLElement {
this.setupEventListeners();
document.addEventListener('click', this.handleOutsideClick);
document.addEventListener('keydown', this.handleKeydown);
+
+ // Subscribe to auth changes (only once!)
+ this.authUnsubscribe = auth.subscribe(() => {
+ this.render();
+ this.setupEventListeners();
+ });
}
disconnectedCallback() {
document.removeEventListener('click', this.handleOutsideClick);
document.removeEventListener('keydown', this.handleKeydown);
+ if (this.authUnsubscribe) this.authUnsubscribe();
}
handleOutsideClick() {
@@ -110,15 +118,15 @@ class AppHeader extends HTMLElement {
+
+ ${auth.isLoggedIn() ? `
+
+ ` : `
+
+ `}
`;
@@ -164,6 +190,21 @@ class AppHeader extends HTMLElement {
});
});
+ // Login button
+ const loginBtn = this.querySelector('#login-btn');
+ loginBtn?.addEventListener('click', () => {
+ const authModal = document.querySelector('auth-modal');
+ if (authModal) {
+ authModal.show('login');
+ }
+ });
+
+ // Profile button
+ const profileBtn = this.querySelector('#profile-btn');
+ profileBtn?.addEventListener('click', () => {
+ router.navigate('/profile');
+ });
+
this.updateThemeIcon();
}
diff --git a/js/components/app-shell.js b/js/components/app-shell.js
index 0dab634..0730bb0 100644
--- a/js/components/app-shell.js
+++ b/js/components/app-shell.js
@@ -1,7 +1,9 @@
import { router } from '../router.js';
import { i18n } from '../i18n.js';
+import { auth } from '../services/auth.js';
import './app-header.js';
import './app-footer.js';
+import './auth-modal.js';
import './pages/page-home.js';
import './pages/page-search.js';
import './pages/page-listing.js';
@@ -29,9 +31,13 @@ class AppShell extends HTMLElement {
+
`;
this.main = this.querySelector('#router-outlet');
+
+ // Try to restore session
+ auth.tryRestoreSession();
}
setupRouter() {
diff --git a/js/services/auth.js b/js/services/auth.js
index 274ce8f..40b4e2d 100644
--- a/js/services/auth.js
+++ b/js/services/auth.js
@@ -20,7 +20,17 @@ class AuthService {
* @returns {string} UUID v4
*/
generateUuid() {
- return crypto.randomUUID();
+ // Use native if available (secure contexts)
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
+ return crypto.randomUUID();
+ }
+
+ // Fallback for non-secure contexts
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ const r = Math.random() * 16 | 0;
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
}
/**
@@ -43,8 +53,18 @@ class AuthService {
try {
await directus.register(email, uuid);
- // Auto-login after registration
- await this.login(uuid);
+ // Try auto-login (may fail if verification required)
+ const loginResult = await this.login(uuid);
+
+ if (!loginResult.success) {
+ // Registration worked but login failed (verification pending)
+ return {
+ uuid,
+ success: true,
+ pendingVerification: true,
+ message: 'Account created. Login may require activation.'
+ };
+ }
return { uuid, success: true };
} catch (error) {
@@ -69,8 +89,6 @@ class AuthService {
await directus.login(email, uuid);
this.currentUser = await directus.getCurrentUser();
this.notifyListeners();
-
- // Store UUID for convenience (optional)
this.storeUuid(uuid);
return { success: true };
diff --git a/js/services/directus.js b/js/services/directus.js
index 51cdc25..bea1317 100644
--- a/js/services/directus.js
+++ b/js/services/directus.js
@@ -100,12 +100,15 @@ class DirectusService {
headers
});
- // Token abgelaufen - versuche Refresh
- if (response.status === 401 && this.refreshToken) {
+ // Token abgelaufen - versuche Refresh (aber nicht für auth-Endpoints)
+ if (response.status === 401 && this.refreshToken && !endpoint.startsWith('/auth/')) {
const refreshed = await this.refreshSession();
if (refreshed) {
headers['Authorization'] = `Bearer ${this.accessToken}`;
return this.request(endpoint, options);
+ } else {
+ // Refresh failed - clear tokens to prevent loops
+ this.clearTokens();
}
}
@@ -216,13 +219,9 @@ class DirectusService {
return false;
}
- async register(email, password, userData = {}) {
- return this.post('/users', {
- email,
- password,
- ...userData,
- role: null // Wird durch Directus Flow/Policy gesetzt
- });
+ async register(email, password) {
+ // Public registration (no verification required)
+ return this.post('/users/register', { email, password });
}
async requestPasswordReset(email) {
diff --git a/locales/de.json b/locales/de.json
index d62c56e..cf3de17 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -3,7 +3,8 @@
"searchPlaceholder": "Was suchst du?",
"createListing": "Anzeige erstellen",
"toggleTheme": "Design wechseln",
- "selectLanguage": "Sprache auswählen"
+ "selectLanguage": "Sprache auswählen",
+ "profile": "Profil"
},
"footer": {
"rights": "Alle Rechte vorbehalten.",
diff --git a/locales/en.json b/locales/en.json
index 09910b4..5bb4639 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -3,7 +3,8 @@
"searchPlaceholder": "What are you looking for?",
"createListing": "Create Listing",
"toggleTheme": "Toggle theme",
- "selectLanguage": "Select language"
+ "selectLanguage": "Select language",
+ "profile": "Profile"
},
"footer": {
"rights": "All rights reserved.",
diff --git a/locales/fr.json b/locales/fr.json
index dee284d..e94f638 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -3,7 +3,8 @@
"searchPlaceholder": "Que cherchez-vous ?",
"createListing": "Créer une annonce",
"toggleTheme": "Changer de thème",
- "selectLanguage": "Choisir la langue"
+ "selectLanguage": "Choisir la langue",
+ "profile": "Profil"
},
"footer": {
"rights": "Tous droits réservés.",
diff --git a/service-worker.js b/service-worker.js
index f49264f..b70cad2 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -1,4 +1,4 @@
-const CACHE_NAME = 'dgray-v20';
+const CACHE_NAME = 'dgray-v26';
const STATIC_ASSETS = [
'/',
'/index.html',
@@ -12,6 +12,8 @@ const STATIC_ASSETS = [
'/js/services/api.js',
'/js/services/crypto.js',
'/js/services/chat.js',
+ '/js/services/directus.js',
+ '/js/services/auth.js',
'/js/data/mock-listings.js',
'/js/components/chat-widget.js',
'/js/components/listing-card.js',