1+ /* =========================================================================
2+ auth-presence.js — Auth, Login-Modal, Presence + generische Modals
3+ =========================================================================
4+ - window.showModal(message, isConfirm?) → Promise<true|false>
5+ - window.showPrompt(message, defaultValue?) → Promise<string|null>
6+ - showLoginModal() → Promise<void>
7+ - setupAuthUI() → Login-State + Online-Counter
8+
9+ Wird in main.js während DOMContentLoaded aufgerufen.
10+ ========================================================================= */
11+
12+ import {
13+ db , auth ,
14+ DATA_COLLECTION , HISTORY_COLLECTION , USER_PROFILES_COLLECTION ,
15+ LOOT_COLLECTION , SNAPSHOTS_COLLECTION ,
16+ rosterDocRef , historyCollectionRef , userProfilesCollectionRef ,
17+ lootCollectionRef , denylistCollectionRef , aliasDocRef , snapshotsCollectionRef ,
18+ doc , setDoc , onSnapshot , collection , deleteDoc , getDoc ,
19+ serverTimestamp , query , orderBy , addDoc , updateDoc , where ,
20+ getDocs , limit ,
21+ onAuthStateChanged , signInWithEmailAndPassword , signOut , signInAnonymously
22+ } from './firebase-init.js' ;
23+
24+ import { state , offensiveBuffsForAssignment , getCurrentRaidId , debounce , debouncedUpdatePools , debouncedUpdateSummary } from './state.js' ;
25+
26+
27+ // =============================================================================
28+ // MODAL-FUNKTION (window.showModal)
29+ // =============================================================================
30+
31+ // ============== MODAL-FUNKTION (global verfügbar) ==============
32+ window . showModal = function ( message , isConfirm = false ) {
33+
34+ // 1. AUFRÄUMEN: Nur dynamische Nachrichten-Fenster löschen!
35+ const allOverlays = document . querySelectorAll ( '.modal-overlay' ) ;
36+ allOverlays . forEach ( el => {
37+ // Wir prüfen anhand der ID, ob es ein "wichtiges" statisches Fenster ist
38+ const isStatic = ( el . id === 'player-edit-modal' || el . id === 'login-modal-overlay' || el . id === 'image-window.lightbox' ) ;
39+
40+ // Nur löschen, wenn es KEIN statisches Fenster ist
41+ if ( ! isStatic ) {
42+ el . remove ( ) ;
43+ }
44+ } ) ;
45+
46+ return new Promise ( ( resolve ) => {
47+ const modalOverlay = document . createElement ( 'div' ) ;
48+ modalOverlay . className = 'modal-overlay' ;
49+ // WICHTIG: Keine ID vergeben, damit es als dynamisch erkannt wird
50+
51+ modalOverlay . innerHTML = `
52+ <div class="modal-content">
53+ <h3>Nachricht</h3>
54+ <p>${ message } </p>
55+ <div class="modal-buttons">
56+ ${ isConfirm ? `<button id="modal-cancel-btn" class="cancel-btn">Abbrechen</button>` : '' }
57+ <button id="modal-ok-btn">OK</button>
58+ </div>
59+ </div>
60+ ` ;
61+ document . body . appendChild ( modalOverlay ) ;
62+
63+ const okButton = modalOverlay . querySelector ( '#modal-ok-btn' ) ;
64+ const cancelButton = modalOverlay . querySelector ( '#modal-cancel-btn' ) ;
65+
66+ const close = ( value ) => {
67+ modalOverlay . remove ( ) ;
68+ resolve ( value ) ;
69+ } ;
70+
71+ okButton . addEventListener ( 'click' , ( ) => close ( true ) ) ;
72+ if ( cancelButton ) {
73+ cancelButton . addEventListener ( 'click' , ( ) => close ( false ) ) ;
74+ }
75+ } ) ;
76+ } ;
77+
78+
79+ // =============================================================================
80+ // PROMPT-FUNKTION (window.showPrompt) — Eingabe-Dialog
81+ // =============================================================================
82+
83+ window . showPrompt = function ( message , defaultValue = '' ) {
84+ return new Promise ( ( resolve ) => {
85+ const modalOverlay = document . createElement ( 'div' ) ;
86+ modalOverlay . className = 'modal-overlay' ;
87+
88+ modalOverlay . innerHTML = `
89+ <div class="modal-content">
90+ <h3>Eingabe</h3>
91+ <p>${ message } </p>
92+ <input type="text" id="prompt-input"
93+ class="w-full bg-slate-900 border border-slate-600 rounded p-2 text-white focus:border-gold outline-none mb-4"
94+ value="${ defaultValue . replace ( / " / g, '"' ) } ">
95+ <div class="modal-buttons">
96+ <button id="prompt-cancel-btn" class="cancel-btn">Abbrechen</button>
97+ <button id="prompt-ok-btn">OK</button>
98+ </div>
99+ </div>
100+ ` ;
101+ document . body . appendChild ( modalOverlay ) ;
102+
103+ const input = modalOverlay . querySelector ( '#prompt-input' ) ;
104+ const okBtn = modalOverlay . querySelector ( '#prompt-ok-btn' ) ;
105+ const cancelBtn = modalOverlay . querySelector ( '#prompt-cancel-btn' ) ;
106+
107+ input . focus ( ) ;
108+ input . select ( ) ;
109+
110+ const close = ( value ) => {
111+ modalOverlay . remove ( ) ;
112+ resolve ( value ) ;
113+ } ;
114+
115+ okBtn . addEventListener ( 'click' , ( ) => close ( input . value . trim ( ) ) ) ;
116+ cancelBtn . addEventListener ( 'click' , ( ) => close ( null ) ) ;
117+ input . addEventListener ( 'keydown' , ( e ) => {
118+ if ( e . key === 'Enter' ) close ( input . value . trim ( ) ) ;
119+ if ( e . key === 'Escape' ) close ( null ) ;
120+ } ) ;
121+ } ) ;
122+ } ;
123+
124+ // =============================================================================
125+ // LOGIN-MODAL
126+ // =============================================================================
127+
128+ // ============== LOGIN MODAL FUNKTION ==============
129+ function showLoginModal ( ) {
130+ return new Promise ( ( resolve ) => {
131+ const loginModalOverlay = document . getElementById ( 'login-modal-overlay' ) ;
132+ const loginForm = document . getElementById ( 'login-form' ) ;
133+ const loginUsernameInput = document . getElementById ( 'login-username' ) ;
134+ const loginPasswordInput = document . getElementById ( 'login-password' ) ;
135+ const loginCancelBtn = document . getElementById ( 'login-modal-cancel-btn' ) ;
136+
137+ loginUsernameInput . value = '' ;
138+ loginPasswordInput . value = '' ;
139+ loginModalOverlay . classList . remove ( 'hidden' ) ;
140+ loginUsernameInput . focus ( ) ;
141+
142+ const handleSubmit = async ( event ) => {
143+ // Verhindert das Neuladen der Seite, was das Standardverhalten eines Formulars ist.
144+ event . preventDefault ( ) ;
145+
146+ const username = loginUsernameInput . value ;
147+ const password = loginPasswordInput . value ;
148+
149+ if ( ! username || ! password ) {
150+ window . showModal ( "Bitte Benutzername und Passwort eingeben." ) ;
151+ return ;
152+ }
153+
154+ try {
155+ // Die Logik zum Anmelden bei Firebase bleibt hier exakt gleich.
156+ const q = query ( userProfilesCollectionRef , where ( "username" , "==" , username ) ) ;
157+ const querySnapshot = await getDocs ( q ) ;
158+
159+ if ( querySnapshot . empty ) {
160+ window . showModal ( "Benutzername nicht gefunden." ) ;
161+ return ;
162+ }
163+ const userProfile = querySnapshot . docs [ 0 ] . data ( ) ;
164+ const userCredential = await signInWithEmailAndPassword ( auth , userProfile . email , password ) ;
165+
166+ if ( userProfile . isManager ) {
167+ sessionStorage . setItem ( 'currentManager' , username ) ;
168+ location . reload ( ) ;
169+ } else {
170+ await signOut ( auth ) ;
171+ window . showModal ( "Dieses Konto ist keinem Gildenrat-Status zugeordnet." ) ;
172+ }
173+ } catch ( error ) {
174+ console . error ( "Login-Fehler:" , error ) ;
175+ window . showModal ( "Falscher Benutzername oder falsches Passwort." ) ;
176+ }
177+ } ;
178+
179+ const handleCancel = ( ) => {
180+ cleanupAndResolve ( false ) ;
181+ } ;
182+
183+ // Hilfsfunktion, um alle Event-Listener wieder zu entfernen und das Modal zu schließen
184+ const cleanupAndResolve = ( value ) => {
185+ loginForm . removeEventListener ( 'submit' , handleSubmit ) ;
186+ loginCancelBtn . removeEventListener ( 'click' , handleCancel ) ;
187+ loginModalOverlay . classList . add ( 'hidden' ) ;
188+ resolve ( value ) ;
189+ }
190+
191+ // Wir lauschen jetzt auf das 'submit'-Ereignis des Formulars
192+ loginForm . addEventListener ( 'submit' , handleSubmit ) ;
193+ loginCancelBtn . addEventListener ( 'click' , handleCancel ) ;
194+
195+ loginModalOverlay . addEventListener ( 'click' , function clickOutside ( event ) {
196+ if ( event . target === loginModalOverlay ) {
197+ handleCancel ( ) ;
198+ }
199+ } ) ;
200+ } ) ;
201+ }
202+
203+
204+ window . showLoginModal = showLoginModal ;
205+
206+ // =============================================================================
207+ // AUTH-UI & PRESENCE-INDICATOR
208+ // =============================================================================
209+
210+ async function setupAuthUI ( ) {
211+ const authSection = document . getElementById ( 'auth-section' ) ;
212+ const currentManagerUsername = sessionStorage . getItem ( 'currentManager' ) ;
213+
214+ if ( currentManagerUsername ) {
215+ const q = query ( userProfilesCollectionRef , where ( "username" , "==" , currentManagerUsername ) ) ;
216+ const querySnapshot = await getDocs ( q ) ;
217+
218+ if ( ! querySnapshot . empty ) {
219+ const userProfile = querySnapshot . docs [ 0 ] . data ( ) ;
220+ if ( userProfile . isManager ) {
221+ window . isManager = true ;
222+ authSection . innerHTML = `<span class="text-white">Angemeldet als: <strong>${ currentManagerUsername } </strong></span><button id="logout-btn" class="ml-2 bg-red-600 hover:bg-red-700 text-white font-bold py-1 px-3 rounded-md text-sm">Logout</button>` ;
223+ document . getElementById ( 'logout-btn' ) . addEventListener ( 'click' , async ( ) => {
224+ await signOut ( auth ) ;
225+ sessionStorage . removeItem ( 'currentManager' ) ;
226+ location . hash = '' ;
227+ location . reload ( ) ;
228+ } ) ;
229+ return ;
230+ }
231+ }
232+ sessionStorage . removeItem ( 'currentManager' ) ;
233+ }
234+
235+ window . isManager = false ;
236+ authSection . innerHTML = `<button id="login-btn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded-md text-sm">Gildenrat-Login</button>` ;
237+ document . getElementById ( 'login-btn' ) . addEventListener ( 'click' , showLoginModal ) ;
238+ }
239+
240+ onAuthStateChanged ( auth , user => {
241+ const presenceIndicator = document . getElementById ( 'presence-indicator' ) ;
242+ if ( window . heartbeatIntervalId ) clearInterval ( window . heartbeatIntervalId ) ;
243+
244+ if ( ! user ) {
245+ signInAnonymously ( auth ) . catch ( e => console . error ( "Anonymer Login-Fehler:" , e ) ) ;
246+ presenceIndicator . innerHTML = `<div class="w-3 h-3 bg-gray-500 rounded-full"></div><span>0</span> Online` ;
247+ return ;
248+ }
249+
250+ const userStatusRef = doc ( db , "presence" , user . uid ) ;
251+ const updatePresenceTimestamp = ( ) => {
252+ setDoc ( userStatusRef , { online : true , last_changed : serverTimestamp ( ) } , { merge : true } ) ;
253+ } ;
254+ updatePresenceTimestamp ( ) ;
255+ window . heartbeatIntervalId = setInterval ( updatePresenceTimestamp , 15 * 60 * 1000 ) ;
256+
257+ onSnapshot ( collection ( db , "presence" ) , snap => {
258+ const nowInSeconds = Date . now ( ) / 1000 ;
259+ const onlineUsersCount = snap . docs . filter ( doc => {
260+ const data = doc . data ( ) ;
261+ return data . online && data . last_changed && ( nowInSeconds - data . last_changed . seconds < 1000 ) ;
262+ } ) . length ;
263+ presenceIndicator . innerHTML = `<div class="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div><span>${ onlineUsersCount } </span> Online` ;
264+ } ) ;
265+ } ) ;
266+
267+ window . setupAuthUI = setupAuthUI ;
0 commit comments