Fix: Hide PWA install button when installed, improve clustering, add debug logs

Fixes three major issues:

1. PWA Install Button (TopBar.jsx):
   - Import isRunningAsPWA() and canInstall() from pwaInstall.js
   - Add state to track if button should be shown
   - Listen to beforeinstallprompt and appinstalled events
   - Hide button when app is already installed or running as PWA

2. Search Suggestions Debug (SmartSearchBar.jsx, recoSearch.js):
   - Add console.log statements to trace search flow
   - Track: search query, results count, errors
   - Helps diagnose why suggestions don't appear
   - recoSearch: Log each endpoint attempt and response

3. Clustering Improvements (usePostsEngine.js, postPriority.js):
   - Reduce maxFullCards: 15 → 12 (fewer full cards)
   - Increase clusterRadius: 0.0015 → 0.002 (~200m for better grouping)
   - Increase minScoreForCard: 35 → 50 (stricter threshold)
   - Reduce default recoScore: 50 → 40 (lower baseline)
   - Add console.log for prioritization results
   - Result: More posts shown as simple markers, less clutter

Debug logs will be visible in browser console to diagnose issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Your Name 2025-12-23 15:23:44 -05:00
parent 76adb74833
commit c659c0267d
5 changed files with 81 additions and 28 deletions

View File

@ -4,7 +4,7 @@ import { registerUser } from "../../api/client";
import AuthToast from "../Auth/AuthToast"; import AuthToast from "../Auth/AuthToast";
// Import pour le bouton PWA // Import pour le bouton PWA
import { promptInstall } from '../../pwaInstall'; import { promptInstall, isRunningAsPWA, canInstall } from '../../pwaInstall';
const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" }; const THEME_LABELS = { dark: "Dark", blue: "Blue", light: "Light" };
const LAST_SIGNUP_KEY = "sociowire:lastSignup"; const LAST_SIGNUP_KEY = "sociowire:lastSignup";
@ -83,6 +83,7 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
const [signupError, setSignupError] = useState(""); const [signupError, setSignupError] = useState("");
const [signupInfo, setSignupInfo] = useState(""); const [signupInfo, setSignupInfo] = useState("");
const [signupLoading, setSignupLoading] = useState(false); const [signupLoading, setSignupLoading] = useState(false);
const [showInstallBtn, setShowInstallBtn] = useState(false);
const openModal = (nextMode) => { const openModal = (nextMode) => {
setMode(nextMode); setMode(nextMode);
@ -100,6 +101,34 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
if (authenticated) setShowAuth(false); if (authenticated) setShowAuth(false);
}, [authenticated]); }, [authenticated]);
// Check if install button should be shown
useEffect(() => {
const checkInstall = () => {
// Hide button if already running as PWA
if (isRunningAsPWA()) {
setShowInstallBtn(false);
return;
}
// Show button if installation is available
setShowInstallBtn(canInstall());
};
checkInstall();
// Listen for beforeinstallprompt to update button visibility
const handlePrompt = () => {
setTimeout(checkInstall, 100);
};
window.addEventListener('beforeinstallprompt', handlePrompt);
window.addEventListener('appinstalled', () => setShowInstallBtn(false));
return () => {
window.removeEventListener('beforeinstallprompt', handlePrompt);
window.removeEventListener('appinstalled', () => setShowInstallBtn(false));
};
}, []);
useEffect(() => { useEffect(() => {
try { try {
const { get, qs, hs, hash } = parseMergedParams(); const { get, qs, hs, hash } = parseMergedParams();
@ -265,26 +294,28 @@ export default function TopBar({ theme = "dark", onChangeTheme }) {
))} ))}
</div> </div>
{/* BOUTON INSTALL PWA ici en haut */} {/* BOUTON INSTALL PWA ici en haut - seulement si pas déjà installé */}
<button {showInstallBtn && (
onClick={() => promptInstall()} <button
style={{ onClick={() => promptInstall()}
marginLeft: '8px', style={{
padding: '5px 10px', marginLeft: '8px',
fontSize: '12px', padding: '5px 10px',
background: '#0ea5e9', fontSize: '12px',
color: 'white', background: '#0ea5e9',
border: 'none', color: 'white',
borderRadius: '999px', border: 'none',
cursor: 'pointer', borderRadius: '999px',
display: 'flex', cursor: 'pointer',
alignItems: 'center', display: 'flex',
gap: '6px', alignItems: 'center',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)', gap: '6px',
}} boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
> }}
<i className="fa-solid fa-download"></i> Install >
</button> <i className="fa-solid fa-download"></i> Install
</button>
)}
</div> </div>
</div> </div>

View File

@ -14,7 +14,7 @@ export function calculatePostPriority(post, viewCenter, allPosts) {
let score = 0; let score = 0;
// 1. Score intrinsèque du post (si disponible depuis reco-service) // 1. Score intrinsèque du post (si disponible depuis reco-service)
const recoScore = typeof post.score === 'number' ? post.score : 50; const recoScore = typeof post.score === 'number' ? post.score : 40; // Réduit de 50 à 40 pour base plus basse
score += recoScore * 0.4; // Weight: 40% score += recoScore * 0.4; // Weight: 40%
// 2. Catégorie (certaines plus importantes) // 2. Catégorie (certaines plus importantes)

View File

@ -87,9 +87,15 @@ export function usePostsEngine({
const viewCenter = center ? { lng: center.lng, lat: center.lat } : null; const viewCenter = center ? { lng: center.lng, lat: center.lat } : null;
const { fullCardPosts, simpleMarkerPosts } = prioritizePosts(visible, viewCenter, { const { fullCardPosts, simpleMarkerPosts } = prioritizePosts(visible, viewCenter, {
maxFullCards: 15, // Max 15 cartes complètes maxFullCards: 12, // Max 12 cartes complètes (réduit de 15)
clusterRadius: 0.0015, // ~150m clustering radius clusterRadius: 0.002, // ~200m clustering radius (augmenté pour plus de groupement)
minScoreForCard: 35, // Score minimum pour carte complète minScoreForCard: 50, // Score minimum pour carte complète (augmenté de 35 à 50)
});
console.log('[usePostsEngine] Prioritized:', {
total: visible.length,
fullCards: fullCardPosts.length,
simpleMarkers: simpleMarkerPosts.length
}); });
// Combine both types for "wanted" tracking // Combine both types for "wanted" tracking

View File

@ -98,12 +98,15 @@ export default function SmartSearchBar({
const t = setTimeout(async () => { const t = setTimeout(async () => {
try { try {
console.log('[SmartSearchBar] Searching for:', query);
const res = await recoSearch(query, { signal: ctrl.signal, limit: 12 }); const res = await recoSearch(query, { signal: ctrl.signal, limit: 12 });
console.log('[SmartSearchBar] Got results:', res?.length || 0, res);
setItems(res); setItems(res);
setOpen(true); setOpen(true);
setActive(res.length ? 0 : -1); setActive(res.length ? 0 : -1);
} catch (e) { } catch (e) {
if (ctrl.signal.aborted) return; if (ctrl.signal.aborted) return;
console.error('[SmartSearchBar] Search error:', e);
setErr((e && e.message) ? e.message : "Search error"); setErr((e && e.message) ? e.message : "Search error");
setItems([]); setItems([]);
} finally { } finally {

View File

@ -76,10 +76,18 @@ async function tryFetchAnyPath(q, { signal } = {}) {
for (const p of paths) { for (const p of paths) {
try { try {
const url = `${p}?q=${encodeURIComponent(query)}`; const url = `${p}?q=${encodeURIComponent(query)}`;
console.log('[recoSearch] Trying:', url);
const res = await fetch(url, { credentials: "include", signal }); const res = await fetch(url, { credentials: "include", signal });
if (res.status === 404) continue; console.log('[recoSearch] Response status:', res.status);
if (!res.ok) return []; // évite spam erreurs if (res.status === 404) {
console.log('[recoSearch] 404, trying next path');
continue;
}
if (!res.ok) {
console.log('[recoSearch] Not OK, returning empty');
return []; // évite spam erreurs
}
const data = await res.json().catch(() => null); const data = await res.json().catch(() => null);
const arr = Array.isArray(data) const arr = Array.isArray(data)
@ -90,11 +98,16 @@ async function tryFetchAnyPath(q, { signal } = {}) {
? data.results ? data.results
: []; : [];
console.log('[recoSearch] Success! Returning', arr.length, 'results from', p);
return arr.map(normalizeOne).filter(Boolean); return arr.map(normalizeOne).filter(Boolean);
} catch (e) { } catch (e) {
// Abort => stop net // Abort => stop net
if (e && (e.name === "AbortError" || e.code === 20)) return []; if (e && (e.name === "AbortError" || e.code === 20)) {
console.log('[recoSearch] Aborted');
return [];
}
// autres erreurs => on teste prochain path // autres erreurs => on teste prochain path
console.log('[recoSearch] Error on', p, ':', e.message, '- trying next');
continue; continue;
} }
} }