|
65 | 65 | }); |
66 | 66 | // Set __app_id to your Firebase projectId for Firestore pathing |
67 | 67 | window.__app_id = "chatv1-5432a"; |
68 | | - window.__initial_auth_token = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; |
| 68 | + window.__initial_auth_token = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; // Kept for consistency, but not used in authentication flow now |
69 | 69 |
|
70 | 70 | // Log the Firebase config as a JSON string and then parsed object for debugging |
71 | 71 | console.log("Firebase config string on load:", window.__firebase_config); |
|
84 | 84 | // and "Missing or insufficient permissions" (which indicate Firestore rules issues): |
85 | 85 | // |
86 | 86 | // 1. **CRITICAL: ENABLE ANONYMOUS AUTHENTICATION IN FIREBASE CONSOLE** |
87 | | - // This app uses anonymous sign-in by default. If this method is not enabled, |
| 87 | + // This app now exclusively uses anonymous sign-in. If this method is not enabled, |
88 | 88 | // Firebase Auth will fail to initialize, leading to "auth/configuration-not-found". |
89 | 89 | // Steps: |
90 | 90 | // a. Go to your Firebase Project Console: https://console.firebase.google.com/ |
|
107 | 107 | // rules_version = '2'; |
108 | 108 | // service cloud.firestore { |
109 | 109 | // match /databases/{database}/documents { |
110 | | - // // This path must match exactly where your messages are stored. |
111 | | - // // window.__app_id is used here, which you've set to "chatv1-5432a" |
| 110 | + // // This path MUST EXACTLY MATCH where your messages are stored. |
| 111 | + // // Ensure 'appId' in the rules matches 'window.__app_id' in your code ("chatv1-5432a"). |
112 | 112 | // match /artifacts/{appId}/public/data/messages/{document=**} { |
113 | 113 | // allow read, write: if request.auth != null; // Allows any authenticated user (including anonymous) |
114 | 114 | // } |
115 | 115 | // } |
116 | 116 | // } |
| 117 | + // Remember to click "Publish" after making changes to your rules. |
117 | 118 | // ==================================================================================== |
118 | 119 |
|
119 | 120 |
|
120 | 121 | // Import Firebase modules from CDN |
121 | 122 | import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js"; |
122 | | - import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js"; |
123 | | - import { getFirestore, collection, addDoc, query, onSnapshot, serverTimestamp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js"; |
| 123 | + import { getAuth, signInAnonymously, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js"; |
| 124 | + import { getFirestore, collection, addDoc, query, onSnapshot, serverTimestamp, doc, deleteDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js"; |
124 | 125 |
|
125 | 126 | // Main App component for the chat application (inlined) |
126 | 127 | function App() { |
|
133 | 134 | const [isAuthReady, setIsAuthReady] = React.useState(false); // To track if authentication is ready |
134 | 135 | const [userName, setUserName] = React.useState(localStorage.getItem('chatUserName') || ''); |
135 | 136 | const [nameInput, setNameInput] = React.useState(''); |
| 137 | + const [isAdmin, setIsAdmin] = React.useState(userName === 'admin'); // Track admin status |
| 138 | + const [showAdminConfirmScreen, setShowAdminConfirmScreen] = React.useState(false); // For second 'admin' prompt |
136 | 139 |
|
137 | 140 |
|
138 | 141 | // Initialize Firebase and handle authentication |
|
146 | 149 | setDb(firestoreDb); |
147 | 150 | setAuth(firebaseAuth); |
148 | 151 |
|
149 | | - // Sign in with custom token if available, otherwise anonymously |
150 | | - if (window.__initial_auth_token) { |
151 | | - signInWithCustomToken(firebaseAuth, window.__initial_auth_token) |
152 | | - .then((userCredential) => { |
153 | | - console.log("Signed in with custom token:", userCredential.user.uid); |
154 | | - }) |
155 | | - .catch(error => { |
156 | | - console.error("Error signing in with custom token:", error); |
157 | | - // Fallback to anonymous sign-in if custom token fails |
158 | | - signInAnonymously(firebaseAuth) |
159 | | - .then((userCredential) => console.log("Signed in anonymously (fallback):", userCredential.user.uid)) |
160 | | - .catch(err => console.error("Error signing in anonymously (fallback):", err)); |
161 | | - }); |
162 | | - } else { |
163 | | - signInAnonymously(firebaseAuth) |
164 | | - .then((userCredential) => console.log("Signed in anonymously:", userCredential.user.uid)) |
165 | | - .catch(error => console.error("Error signing in anonymously:", error)); |
166 | | - } |
| 152 | + // Sign in anonymously (removed custom token logic as it's not applicable for static hosting without backend) |
| 153 | + signInAnonymously(firebaseAuth) |
| 154 | + .then((userCredential) => console.log("Signed in anonymously:", userCredential.user.uid)) |
| 155 | + .catch(error => console.error("Error signing in anonymously:", error)); |
167 | 156 |
|
168 | 157 | // Listen for authentication state changes |
169 | 158 | const unsubscribeAuth = onAuthStateChanged(firebaseAuth, (user) => { |
|
172 | 161 | setIsAuthReady(true); |
173 | 162 | console.log("Auth state changed: User is authenticated. User ID:", user.uid, "isAuthReady:", true); |
174 | 163 | } else { |
175 | | - // Only set userId to random UUID if auth failed or user is not found, |
176 | | - // AND only if isAuthReady hasn't already been set by a successful auth. |
177 | | - // The anonymous sign-in ensures a user always exists for this app's purpose. |
178 | | - if (!userId) { // Check if userId is not already set by a successful login |
| 164 | + if (!userId) { |
179 | 165 | setUserId(crypto.randomUUID()); |
180 | 166 | } |
181 | | - setIsAuthReady(true); // Always set to true once auth state is determined |
| 167 | + setIsAuthReady(true); |
182 | 168 | console.log("Auth state changed: User is NOT authenticated. (Anonymous expected). isAuthReady:", true); |
183 | 169 | } |
184 | 170 | }); |
|
198 | 184 | const chatCollectionRef = collection(db, `artifacts/${window.__app_id}/public/data/messages`); |
199 | 185 |
|
200 | 186 | // Create a query to order messages by timestamp |
201 | | - // Data will be sorted in memory. |
202 | 187 | const q = query(chatCollectionRef); |
203 | 188 |
|
204 | 189 | // Subscribe to real-time updates |
|
207 | 192 | id: doc.id, |
208 | 193 | ...doc.data() |
209 | 194 | })); |
210 | | - // Sort messages by timestamp in memory |
211 | 195 | fetchedMessages.sort((a, b) => (a.timestamp?.toMillis() || 0) - (b.timestamp?.toMillis() || 0)); |
212 | 196 | setMessages(fetchedMessages); |
213 | 197 | }, (error) => { |
|
224 | 208 | if (newMessage.trim() === '' || !db || !userId) return; |
225 | 209 |
|
226 | 210 | try { |
227 | | - // Add a new document to the 'messages' collection |
228 | 211 | await addDoc(collection(db, `artifacts/${window.__app_id}/public/data/messages`), { |
229 | 212 | text: newMessage, |
230 | 213 | userId: userId, |
231 | | - userName: userName || 'Anonymous', // Store the user's chosen name |
232 | | - timestamp: serverTimestamp() // Firestore server timestamp for consistency |
| 214 | + userName: userName || 'Anonymous', |
| 215 | + timestamp: serverTimestamp() |
233 | 216 | }); |
234 | | - setNewMessage(''); // Clear the input field after sending |
| 217 | + setNewMessage(''); |
235 | 218 | } catch (error) { |
236 | 219 | console.error("Error sending message:", error); |
237 | 220 | } |
238 | 221 | }; |
239 | 222 |
|
| 223 | + // Function to delete a message (only for admin) |
| 224 | + const deleteMessage = async (messageId) => { |
| 225 | + if (!db || !isAdmin) { // Only allow if db is ready and user is admin |
| 226 | + console.warn("Permission denied: Only admin can delete messages."); |
| 227 | + return; |
| 228 | + } |
| 229 | + try { |
| 230 | + const messageRef = doc(db, `artifacts/${window.__app_id}/public/data/messages`, messageId); |
| 231 | + await deleteDoc(messageRef); |
| 232 | + console.log(`Message ${messageId} deleted successfully.`); |
| 233 | + } catch (error) { |
| 234 | + console.error("Error deleting message:", error); |
| 235 | + } |
| 236 | + }; |
| 237 | + |
240 | 238 | // Handle key press for sending message (e.g., Enter key) |
241 | 239 | const handleKeyPress = (e) => { |
242 | 240 | if (e.key === 'Enter') { |
243 | 241 | sendMessage(); |
244 | 242 | } |
245 | 243 | }; |
246 | 244 |
|
247 | | - // Function to set the user's name |
| 245 | + // Function to handle initial name setting or admin first attempt |
248 | 246 | const setChatUserName = () => { |
249 | | - if (nameInput.trim() !== '') { |
| 247 | + if (nameInput.trim() === 'admin') { |
| 248 | + setShowAdminConfirmScreen(true); // Show confirmation for admin |
| 249 | + setNameInput(''); // Clear input for re-entry |
| 250 | + } else if (nameInput.trim() !== '') { |
250 | 251 | setUserName(nameInput.trim()); |
251 | | - localStorage.setItem('chatUserName', nameInput.trim()); // Persist name |
| 252 | + localStorage.setItem('chatUserName', nameInput.trim()); |
| 253 | + setIsAdmin(false); // Not admin if name is not 'admin' |
| 254 | + setShowAdminConfirmScreen(false); |
252 | 255 | } |
253 | 256 | }; |
254 | 257 |
|
255 | | - // Conditional rendering: Show name input if userName is not set |
256 | | - if (!userName) { |
| 258 | + // Function to confirm admin access on the second 'admin' entry |
| 259 | + const confirmAdminAccess = () => { |
| 260 | + if (nameInput.trim() === 'admin') { |
| 261 | + setUserName('admin'); |
| 262 | + localStorage.setItem('chatUserName', 'admin'); |
| 263 | + setIsAdmin(true); // Grant admin rights |
| 264 | + setShowAdminConfirmScreen(false); |
| 265 | + setNameInput(''); // Clear input |
| 266 | + } else { |
| 267 | + // If wrong password for admin, reset to initial name input |
| 268 | + setUserName(''); |
| 269 | + localStorage.removeItem('chatUserName'); |
| 270 | + setIsAdmin(false); |
| 271 | + setShowAdminConfirmScreen(false); |
| 272 | + setNameInput(''); |
| 273 | + alert('Incorrect input for admin. Please enter your name again.'); // Simple message for incorrect entry |
| 274 | + } |
| 275 | + }; |
| 276 | + |
| 277 | + // Conditional rendering: Show name input or admin confirmation |
| 278 | + if (!userName || showAdminConfirmScreen) { |
257 | 279 | return React.createElement( |
258 | 280 | "div", |
259 | 281 | { |
|
264 | 286 | { |
265 | 287 | className: "text-2xl font-bold text-gray-800 mb-6" |
266 | 288 | }, |
267 | | - "Welcome to the Chat!" |
| 289 | + showAdminConfirmScreen ? "Confirm Admin Access" : "Welcome to the Chat!" |
268 | 290 | ), |
269 | 291 | React.createElement( |
270 | 292 | "p", |
271 | 293 | { |
272 | 294 | className: "text-gray-600 mb-4" |
273 | 295 | }, |
274 | | - "Please enter your name to start chatting." |
| 296 | + showAdminConfirmScreen ? "Please re-enter 'admin' to confirm administrator privileges." : "Please enter your name to start chatting." |
275 | 297 | ), |
276 | 298 | React.createElement( |
277 | 299 | "input", |
278 | 300 | { |
279 | 301 | type: "text", |
280 | 302 | value: nameInput, |
281 | 303 | onChange: e => setNameInput(e.target.value), |
282 | | - onKeyPress: (e) => { if (e.key === 'Enter') setChatUserName(); }, |
283 | | - placeholder: "Your Name", |
| 304 | + onKeyPress: (e) => { |
| 305 | + if (e.key === 'Enter') { |
| 306 | + if (showAdminConfirmScreen) { |
| 307 | + confirmAdminAccess(); |
| 308 | + } else { |
| 309 | + setChatUserName(); |
| 310 | + } |
| 311 | + } |
| 312 | + }, |
| 313 | + placeholder: showAdminConfirmScreen ? "Type 'admin' again" : "Your Name", |
284 | 314 | className: "p-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200 w-full max-w-sm mb-4" |
285 | 315 | } |
286 | 316 | ), |
287 | 317 | React.createElement( |
288 | 318 | "button", |
289 | 319 | { |
290 | | - onClick: setChatUserName, |
| 320 | + onClick: showAdminConfirmScreen ? confirmAdminAccess : setChatUserName, |
291 | 321 | className: "px-8 py-3 bg-blue-600 text-white font-semibold rounded-xl shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200" |
292 | 322 | }, |
293 | | - "Start Chatting" |
| 323 | + showAdminConfirmScreen ? "Confirm" : "Start Chatting" |
294 | 324 | ) |
295 | 325 | ); |
296 | 326 | } |
|
340 | 370 | onClick: () => { |
341 | 371 | setUserName(''); // Clear name in state |
342 | 372 | localStorage.removeItem('chatUserName'); // Clear from localStorage |
| 373 | + setIsAdmin(false); // Remove admin status |
343 | 374 | setNewMessage(''); // Clear any message in input |
344 | 375 | setMessages([]); // Clear messages to reset view |
| 376 | + setShowAdminConfirmScreen(false); // Ensure admin confirm screen is hidden |
345 | 377 | }, |
346 | 378 | className: "ml-4 px-3 py-1 bg-blue-700 text-white text-xs rounded-full hover:bg-blue-800 transition duration-200" |
347 | 379 | }, |
|
403 | 435 | className: "text-right text-xs mt-1 opacity-75" |
404 | 436 | }, |
405 | 437 | new Date(msg.timestamp.toMillis()).toLocaleTimeString() |
| 438 | + ), |
| 439 | + // Render delete button only if current user is admin |
| 440 | + isAdmin && React.createElement( |
| 441 | + "button", |
| 442 | + { |
| 443 | + onClick: () => deleteMessage(msg.id), |
| 444 | + className: "ml-2 px-2 py-1 bg-red-500 text-white text-xs rounded-full hover:bg-red-600 transition duration-200 self-end" |
| 445 | + }, |
| 446 | + "Delete" |
406 | 447 | ) |
407 | 448 | ) |
408 | 449 | ); |
|
429 | 470 | "button", |
430 | 471 | { |
431 | 472 | onClick: sendMessage, |
432 | | - className: "px-6 py-3 bg-blue-600 text-white font-semibold rounded-xl shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed", |
433 | | - disabled: !isAuthReady || newMessage.trim() === '' |
| 473 | + className: "px-6 py-3 bg-blue-600 text-white font-semibold rounded-xl shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200 disabled:opacity-50 disabled:cursor-not-allowed" |
434 | 474 | }, |
435 | 475 | "Send" |
436 | 476 | ) |
|
0 commit comments