Fix UX issues: Suppress PWA in dev, respect onboarding preference, silence WS warnings

PWA Install Prompt:
- Suppress in development (process.env.NODE_ENV === 'development')
- Remember dismissal for 7 days (localStorage 'pwa_prompt_dismissed')
- Delay showing by 30 seconds in production to avoid interrupting users

Onboarding Tour:
- Now respects 'Dont Show Again' checkbox (isDontShowAgain check added)
- 1-second delay before showing (smoother UX)
- Won't appear if user previously checked 'Dont show again'

WebSocket Connection:
- Removed console.log/warn for connect/disconnect/error events
- Increased reconnection delay from 1s to 5s (less spam)
- Reduced reconnection attempts from 10 to 3
- Increased timeout from 10s to 15s
- Errors now silently fail (WebSocket is optional for real-time collaboration)
This commit is contained in:
Z User committed 2026-08-10 20:51:24 +00:00
1 parent 0c7a69ffe0
commit 13fce9d906
3 files changed
+32 -14

No files matched your search

+5 -4
View File
@@ -126,7 +126,7 @@ import { t } from '@/lib/i18n';
import { fetchSchoolYears, fetchStudents, fetchClasses, fetchDBNotifications, markAllNotificationsRead, markSingleNotificationRead, type SchoolYear, type Student, type ClassGroup, type DBNotification, type DBNotificationData } from '@/lib/api'; import { fetchSchoolYears, fetchStudents, fetchClasses, fetchDBNotifications, markAllNotificationsRead, markSingleNotificationRead, type SchoolYear, type Student, type ClassGroup, type DBNotification, type DBNotificationData } from '@/lib/api';
import { apiGet } from '@/lib/api'; import { apiGet } from '@/lib/api';
import type { School as SchoolType } from '@/lib/api'; import type { School as SchoolType } from '@/lib/api';
import OnboardingTour, { isOnboardingCompleted } from '@/components/onboarding-tour'; import OnboardingTour, { isOnboardingCompleted, isDontShowAgain } from '@/components/onboarding-tour';
import KeyboardShortcutsDialog from '@/components/keyboard-shortcuts-dialog'; import KeyboardShortcutsDialog from '@/components/keyboard-shortcuts-dialog';
import { import {
CommandDialog, CommandDialog,
@@ -755,10 +755,11 @@ export default function AppLayout() {
setCurrentView('classes'); setCurrentView('classes');
}, [setCurrentClass, setCurrentView]); }, [setCurrentClass, setCurrentView]);
// Check onboarding status on mount // Check onboarding status on mount - respect "don't show again" preference
useEffect(() => { useEffect(() => {
if (!isOnboardingCompleted()) { if (!isOnboardingCompleted() && !isDontShowAgain()) {
setOnboardingOpen(true); const timer = setTimeout(() => setOnboardingOpen(true), 1000);
return () => clearTimeout(timer);
} }
}, []); }, []);
+22 -2
View File
@@ -94,10 +94,27 @@ export function PWAInstallPrompt() {
return; return;
} }
// Don't show PWA prompt in development
if (process.env.NODE_ENV === 'development') {
return;
}
// Check if user dismissed recently (within 7 days)
try {
const dismissedAt = localStorage.getItem('pwa_prompt_dismissed');
if (dismissedAt) {
const daysSince = (Date.now() - parseInt(dismissedAt)) / (1000 * 60 * 60 * 24);
if (daysSince < 7) return;
}
} catch {
// ignore
}
const handleBeforeInstall = (e: Event) => { const handleBeforeInstall = (e: Event) => {
e.preventDefault(); e.preventDefault();
setDeferredPrompt(e as BeforeInstallPromptEvent); setDeferredPrompt(e as BeforeInstallPromptEvent);
setShowInstall(true); // Delay showing by 30 seconds to avoid interrupting the user
setTimeout(() => setShowInstall(true), 30000);
}; };
const handleAppInstalled = () => { const handleAppInstalled = () => {
@@ -155,7 +172,10 @@ export function PWAInstallPrompt() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => setShowInstall(false)} onClick={() => {
setShowInstall(false);
try { localStorage.setItem('pwa_prompt_dismissed', String(Date.now())); } catch {}
}}
className="h-8 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" className="h-8 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
> >
{t('action.cancel')} {t('action.cancel')}
+5 -8
View File
@@ -73,26 +73,23 @@ function connectSocket(userId: string, userName: string, schoolId: string, role:
const socket = io('/?XTransformPort=3003', { const socket = io('/?XTransformPort=3003', {
transports: ['websocket', 'polling'], transports: ['websocket', 'polling'],
reconnection: true, reconnection: true,
reconnectionDelay: 1000, reconnectionDelay: 5000,
reconnectionAttempts: 10, reconnectionAttempts: 3,
timeout: 10000, timeout: 15000,
}); });
socket.on('connect', () => { socket.on('connect', () => {
console.log('[CT-WS] Connected to WebSocket service');
socketConnected = true; socketConnected = true;
// Authenticate
socket.emit('auth', { userId, userName, schoolId, role }); socket.emit('auth', { userId, userName, schoolId, role });
}); });
socket.on('disconnect', () => { socket.on('disconnect', () => {
console.log('[CT-WS] Disconnected from WebSocket service');
socketConnected = false; socketConnected = false;
}); });
socket.on('connect_error', (err) => { socket.on('connect_error', () => {
console.warn('[CT-WS] Connection error:', err.message);
socketConnected = false; socketConnected = false;
// Silently fail - WebSocket is optional (real-time collaboration only)
}); });
socketInstance = socket; socketInstance = socket;