4sp-dv 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1116,8 +1116,12 @@
1116
1116
 
1117
1117
  const docRef = doc(db, 'analytics', sessionId);
1118
1118
 
1119
+ // --- FIX: Determine userId before writing ---
1120
+ const currentUid = (auth.currentUser && auth.currentUser.uid) ? auth.currentUser.uid : 'anonymous';
1121
+
1119
1122
  // Using Firestore functions imported at top
1120
1123
  setDoc(docRef, {
1124
+ userId: currentUid, // <--- ADDED THIS FIELD
1121
1125
  sessionId: sessionId,
1122
1126
  userAgent: navigator.userAgent,
1123
1127
  lastActive: serverTimestamp(),
@@ -1274,7 +1278,66 @@
1274
1278
  // Initial fetch to check admin status (less frequent change)
1275
1279
  const adminDoc = await getDoc(doc(db, "admins", ownerUid));
1276
1280
 
1277
- userDataUnsubscribe = onSnapshot(doc(db, "users", ownerUid), (docSnap) => {
1281
+ userDataUnsubscribe = onSnapshot(doc(db, "users", ownerUid), async (docSnap) => {
1282
+ // --- INTEGRITY CHECK START ---
1283
+ let needsRepair = false;
1284
+ let missingFields = [];
1285
+
1286
+ if (!docSnap.exists()) {
1287
+ console.warn("User document missing. Attempting repair...");
1288
+ needsRepair = true;
1289
+ missingFields = ['document_missing'];
1290
+ } else {
1291
+ const d = docSnap.data();
1292
+ // Required fields based on connection.html schema
1293
+ if (!d.email) missingFields.push('email');
1294
+ if (!d.username) missingFields.push('username');
1295
+ if (!d.authMethod) missingFields.push('authMethod');
1296
+ if (!d.createdAt) missingFields.push('createdAt');
1297
+ // emailVerified is boolean, check for undefined
1298
+ if (d.emailVerified === undefined) missingFields.push('emailVerified');
1299
+
1300
+ if (missingFields.length > 0) {
1301
+ console.warn("User document incomplete:", missingFields);
1302
+ needsRepair = true;
1303
+ }
1304
+ }
1305
+
1306
+ if (needsRepair) {
1307
+ const user = auth.currentUser;
1308
+ if (user && user.uid === ownerUid) {
1309
+ try {
1310
+ const repairData = {
1311
+ email: user.email,
1312
+ // Guess auth method or default to unknown if complex
1313
+ authMethod: user.providerData.length > 0 ? user.providerData[0].providerId : 'unknown',
1314
+ username: user.displayName || user.email.split('@')[0].replace(/[^a-zA-Z0-9]/g, '').slice(0,16),
1315
+ emailVerified: user.emailVerified,
1316
+ photoURL: user.photoURL || null
1317
+ };
1318
+
1319
+ // Add createdAt only if missing to avoid overwriting existing timestamp (if any)
1320
+ // If doc missing, we need it.
1321
+ if (!docSnap.exists() || !docSnap.data().createdAt) {
1322
+ repairData.createdAt = serverTimestamp();
1323
+ }
1324
+
1325
+ await setDoc(doc(db, "users", ownerUid), repairData, { merge: true });
1326
+ console.log("User document auto-repaired.");
1327
+ // The snapshot listener will fire again with the fix, so we return here to avoid double processing UI
1328
+ return;
1329
+ } catch (err) {
1330
+ console.error("Auto-repair failed:", err);
1331
+ showIntegrityAlert(missingFields);
1332
+ }
1333
+ } else {
1334
+ showIntegrityAlert(missingFields);
1335
+ }
1336
+ } else {
1337
+ hideIntegrityAlert();
1338
+ }
1339
+ // --- INTEGRITY CHECK END ---
1340
+
1278
1341
  if (docSnap.exists()) {
1279
1342
  const data = docSnap.data();
1280
1343
  // Merge with existing local data to preserve properties not in basic user doc if any
@@ -1325,6 +1388,40 @@
1325
1388
  initAnalytics(db, ownerUid);
1326
1389
  }
1327
1390
  }
1391
+
1392
+ function showIntegrityAlert(issues) {
1393
+ let alertBox = document.getElementById('integrity-alert-box');
1394
+ if (!alertBox) {
1395
+ alertBox = document.createElement('div');
1396
+ alertBox.id = 'integrity-alert-box';
1397
+ alertBox.className = 'fixed bottom-4 right-4 bg-red-900/90 border border-red-500/50 text-white px-4 py-3 rounded-xl shadow-2xl flex items-center gap-4 z-[10000] backdrop-blur-md transition-all duration-300 transform translate-y-20 opacity-0';
1398
+ alertBox.innerHTML = `
1399
+ <div class="flex items-center gap-3">
1400
+ <i class="fa-solid fa-triangle-exclamation text-red-400 text-xl"></i>
1401
+ <div class="flex flex-col">
1402
+ <span class="font-bold text-sm">Account Sync Error</span>
1403
+ <span class="text-xs text-gray-300">Your account data is incomplete.</span>
1404
+ </div>
1405
+ </div>
1406
+ <a href="https://4sp-organization.github.io/connection.html" target="_blank" class="bg-white/10 hover:bg-white/20 text-white text-xs font-medium py-2 px-4 rounded-lg transition border border-white/10 whitespace-nowrap">
1407
+ Redirect <i class="fa-solid fa-arrow-right ml-1"></i>
1408
+ </a>
1409
+ `;
1410
+ document.body.appendChild(alertBox);
1411
+ // Animate in
1412
+ setTimeout(() => {
1413
+ alertBox.classList.remove('translate-y-20', 'opacity-0');
1414
+ }, 100);
1415
+ }
1416
+ }
1417
+
1418
+ function hideIntegrityAlert() {
1419
+ const alertBox = document.getElementById('integrity-alert-box');
1420
+ if (alertBox) {
1421
+ alertBox.classList.add('translate-y-20', 'opacity-0');
1422
+ setTimeout(() => alertBox.remove(), 300);
1423
+ }
1424
+ }
1328
1425
 
1329
1426
  // Helper to update the settings panel preview if open
1330
1427
  function updateSettingsPfpPreview(user, previewEl) {
@@ -369,38 +369,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
369
369
  </div>
370
370
  </div>
371
371
 
372
- <button id="stats-btn" class="fixed bottom-8 left-8 w-10 h-10 rounded-xl bg-black border border-[#333] text-gray-400 flex items-center justify-center hover:bg-[#111] hover:text-white hover:scale-110 transition-all duration-300 z-50">
373
- <i class="fa-solid fa-chart-simple"></i>
374
- </button>
375
372
 
376
- <div id="stats-dropdown" class="fixed bottom-20 left-8 bg-black border border-[#333] rounded-[18px] p-4 min-w-[240px] opacity-0 pointer-events-none transform translate-y-4 transition-all duration-300 z-50 shadow-2xl">
377
- <div class="flex items-center gap-3 mb-4 pb-3 border-b border-[#222]">
378
- <div class="w-10 h-10 rounded-full bg-[#111] flex items-center justify-center text-white font-bold text-lg" id="stat-avatar">
379
- ?
380
- </div>
381
- <div>
382
- <div class="text-white text-sm font-medium" id="stat-email">Loading...</div>
383
- <div class="text-xs text-gray-500 font-mono" id="stat-uid">...</div>
384
- </div>
385
- </div>
386
-
387
- <div class="space-y-3">
388
- <div>
389
- <div class="text-[10px] uppercase tracking-wider text-gray-600 font-semibold mb-1">Member Since</div>
390
- <div class="text-gray-300 text-sm" id="stat-created">...</div>
391
- </div>
392
- <div>
393
- <div class="text-[10px] uppercase tracking-wider text-gray-600 font-semibold mb-1">Last Sign In</div>
394
- <div class="text-gray-300 text-sm" id="stat-last-login">...</div>
395
- </div>
396
- <div>
397
- <div class="text-[10px] uppercase tracking-wider text-gray-600 font-semibold mb-1">Provider</div>
398
- <div class="text-gray-300 text-sm flex items-center gap-2" id="stat-provider">
399
- ...
400
- </div>
401
- </div>
402
- </div>
403
- </div>
404
373
 
405
374
  <button id="retry-weather-btn" onclick="retryWeather()" title="Retry Weather">
406
375
  <i class="fa-solid fa-cloud-sun"></i>
@@ -410,57 +379,8 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
410
379
  import { firebaseConfig } from '../firebase-config.js';
411
380
 
412
381
  // --- STATS DROPDOWN LOGIC ---
413
- const statsBtn = document.getElementById('stats-btn');
414
- const statsDropdown = document.getElementById('stats-dropdown');
415
-
416
- if(statsBtn && statsDropdown) {
417
- statsBtn.addEventListener('click', (e) => {
418
- e.stopPropagation();
419
- const isVisible = statsDropdown.classList.contains('opacity-100');
420
- if (isVisible) {
421
- statsDropdown.classList.remove('opacity-100', 'translate-y-0', 'pointer-events-auto');
422
- statsDropdown.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
423
- } else {
424
- statsDropdown.classList.add('opacity-100', 'translate-y-0', 'pointer-events-auto');
425
- statsDropdown.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
426
- }
427
- });
428
-
429
- // Close when clicking outside
430
- document.addEventListener('click', (e) => {
431
- if (!statsDropdown.contains(e.target) && !statsBtn.contains(e.target)) {
432
- statsDropdown.classList.remove('opacity-100', 'translate-y-0', 'pointer-events-auto');
433
- statsDropdown.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
434
- }
435
- });
436
- }
382
+ // Removed as per request.
437
383
 
438
- function populateStats(user) {
439
- if (!user) return;
440
-
441
- // Email & UID
442
- document.getElementById('stat-email').textContent = user.email || 'Anonymous';
443
- document.getElementById('stat-uid').textContent = user.uid.substring(0, 12) + '...';
444
- document.getElementById('stat-avatar').textContent = (user.email || 'A').charAt(0).toUpperCase();
445
-
446
- // Dates
447
- const created = new Date(user.metadata.creationTime);
448
- const lastLogin = new Date(user.metadata.lastSignInTime);
449
-
450
- document.getElementById('stat-created').textContent = created.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
451
- document.getElementById('stat-last-login').textContent = lastLogin.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' at ' + lastLogin.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
452
-
453
- // Provider
454
- const providerId = user.providerData[0]?.providerId || 'password';
455
- let providerIcon = 'fa-envelope';
456
- let providerName = 'Email';
457
-
458
- if (providerId.includes('google')) { providerIcon = 'fa-google'; providerName = 'Google'; }
459
- else if (providerId.includes('github')) { providerIcon = 'fa-github'; providerName = 'GitHub'; }
460
- else if (providerId.includes('microsoft')) { providerIcon = 'fa-microsoft'; providerName = 'Microsoft'; }
461
-
462
- document.getElementById('stat-provider').innerHTML = `<i class="fa-brands ${providerIcon}"></i> ${providerName}`;
463
- }
464
384
 
465
385
  // --- GLOBAL STATE ---
466
386
  let CURRENT_LAT = null;
@@ -802,7 +722,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
802
722
  if (firebase.auth) {
803
723
  firebase.auth().onAuthStateChanged((user) => {
804
724
  if (user) {
805
- populateStats(user);
725
+ // Stats removed
806
726
  } else if (!window._LOCAL_MODE) {
807
727
  window.location.replace(redirectPath);
808
728
  }
package/navigation.js CHANGED
@@ -603,8 +603,8 @@ let db;
603
603
  const container = document.getElementById('navbar-container');
604
604
  if (!container) return;
605
605
 
606
- const navElement = container.querySelector('nav');
607
- const tabWrapper = navElement.querySelector('.tab-wrapper');
606
+ // Fix for navigation.js:607 crash
607
+ const tabWrapper = container.querySelector('.tab-wrapper');
608
608
  const authControlsWrapper = document.getElementById('auth-controls-wrapper');
609
609
  const navbarLogo = document.getElementById('navbar-logo');
610
610
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "4sp-dv",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/v5-4simpleproblems/v5-4simpleproblems-dv#readme",