fix: React-Hooks in Demo-Tour und LogEntriesList bereinigen
Tour-Schritte über zentralen Effect synchronisieren, Escape-Listener per Ref stabilisieren und Eintragsliste nur bei Logbook-Wechsel bzw. Rückkehr aus dem Editor neu laden. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+6
-6
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import { DialogProvider } from './components/ModalDialog.tsx'
|
import { DialogProvider } from './components/ModalDialog.tsx'
|
||||||
import AuthOnboarding from './components/AuthOnboarding.tsx'
|
import AuthOnboarding from './components/AuthOnboarding.tsx'
|
||||||
@@ -141,14 +141,14 @@ function App() {
|
|||||||
}
|
}
|
||||||
}, [isAuthenticated, activeLogbookId])
|
}, [isAuthenticated, activeLogbookId])
|
||||||
|
|
||||||
const handleSelectLogbook = useCallback((id: string, title: string) => {
|
const selectLogbook = (id: string, title: string) => {
|
||||||
setActiveLogbookId(id)
|
setActiveLogbookId(id)
|
||||||
setActiveLogbookTitle(title)
|
setActiveLogbookTitle(title)
|
||||||
setActiveTab('logs')
|
setActiveTab('logs')
|
||||||
setTourSelectedEntryId(null)
|
setTourSelectedEntryId(null)
|
||||||
localStorage.setItem('active_logbook_id', id)
|
localStorage.setItem('active_logbook_id', id)
|
||||||
localStorage.setItem('active_logbook_title', title)
|
localStorage.setItem('active_logbook_title', title)
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
const handleAuthenticated = async () => {
|
const handleAuthenticated = async () => {
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
@@ -156,7 +156,7 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
const demo = await seedDemoLogbookIfNeeded()
|
const demo = await seedDemoLogbookIfNeeded()
|
||||||
if (demo) {
|
if (demo) {
|
||||||
handleSelectLogbook(demo.logbookId, demo.title)
|
selectLogbook(demo.logbookId, demo.title)
|
||||||
if (demo.firstEntryId) {
|
if (demo.firstEntryId) {
|
||||||
setDemoHighlightEntryId(demo.firstEntryId)
|
setDemoHighlightEntryId(demo.firstEntryId)
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ function App() {
|
|||||||
onAccepted={(logbookId, title) => {
|
onAccepted={(logbookId, title) => {
|
||||||
setIsAuthenticated(true)
|
setIsAuthenticated(true)
|
||||||
setIsAcceptingInvite(false)
|
setIsAcceptingInvite(false)
|
||||||
handleSelectLogbook(logbookId, title)
|
selectLogbook(logbookId, title)
|
||||||
// Clean URL query parameters and hash anchor
|
// Clean URL query parameters and hash anchor
|
||||||
window.history.replaceState({}, document.title, window.location.pathname)
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
}}
|
}}
|
||||||
@@ -237,7 +237,7 @@ function App() {
|
|||||||
<div style={{ display: 'contents' }}>
|
<div style={{ display: 'contents' }}>
|
||||||
{pwaInstallBanner}
|
{pwaInstallBanner}
|
||||||
<LogbookDashboard
|
<LogbookDashboard
|
||||||
onSelectLogbook={handleSelectLogbook}
|
onSelectLogbook={selectLogbook}
|
||||||
onLogout={handleLogout}
|
onLogout={handleLogout}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useLayoutEffect, useState } from 'react'
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { X, ChevronLeft, ChevronRight } from 'lucide-react'
|
import { X, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +34,8 @@ export default function AppTourOverlay() {
|
|||||||
} = useAppTour()
|
} = useAppTour()
|
||||||
|
|
||||||
const [spotlight, setSpotlight] = useState<SpotlightRect | null>(null)
|
const [spotlight, setSpotlight] = useState<SpotlightRect | null>(null)
|
||||||
|
const skipTourRef = useRef(skipTour)
|
||||||
|
skipTourRef.current = skipTour
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isActive || !currentStepId || isCenteredTourStep(currentStepId)) {
|
if (!isActive || !currentStepId || isCenteredTourStep(currentStepId)) {
|
||||||
@@ -94,11 +96,11 @@ export default function AppTourOverlay() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') skipTour()
|
if (event.key === 'Escape') skipTourRef.current()
|
||||||
}
|
}
|
||||||
window.addEventListener('keydown', onKeyDown)
|
window.addEventListener('keydown', onKeyDown)
|
||||||
return () => window.removeEventListener('keydown', onKeyDown)
|
return () => window.removeEventListener('keydown', onKeyDown)
|
||||||
}, [isActive, skipTour])
|
}, [isActive])
|
||||||
|
|
||||||
if (!isActive || !currentStepId) return null
|
if (!isActive || !currentStepId) return null
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { db } from '../services/db.js'
|
import { db } from '../services/db.js'
|
||||||
import { getActiveMasterKey } from '../services/auth.js'
|
import { getActiveMasterKey } from '../services/auth.js'
|
||||||
@@ -70,14 +70,9 @@ export default function LogEntriesList({
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [exporting, setExporting] = useState(false)
|
const [exporting, setExporting] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const prevSelectedEntryIdRef = useRef<string | null | undefined>(undefined)
|
||||||
|
|
||||||
useEffect(() => {
|
const loadEntries = useCallback(async () => {
|
||||||
if (!selectedEntryId) {
|
|
||||||
loadEntries()
|
|
||||||
}
|
|
||||||
}, [logbookId, selectedEntryId])
|
|
||||||
|
|
||||||
const loadEntries = async () => {
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
@@ -136,7 +131,20 @@ export default function LogEntriesList({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}, [logbookId, readOnly, preloadedEntries])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadEntries()
|
||||||
|
}, [loadEntries])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prevSelectedEntryId = prevSelectedEntryIdRef.current
|
||||||
|
prevSelectedEntryIdRef.current = selectedEntryId
|
||||||
|
|
||||||
|
if (prevSelectedEntryId !== undefined && prevSelectedEntryId !== null && selectedEntryId === null) {
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
}, [selectedEntryId, loadEntries])
|
||||||
|
|
||||||
const handleDownloadCsv = async () => {
|
const handleDownloadCsv = async () => {
|
||||||
setExporting(true)
|
setExporting(true)
|
||||||
|
|||||||
@@ -115,9 +115,7 @@ export function AppTourProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
setStepIndex(0)
|
setStepIndex(0)
|
||||||
setIsActive(true)
|
setIsActive(true)
|
||||||
applyStepSideEffects(STEP_ORDER[0])
|
}, [])
|
||||||
scrollToCurrentTarget(STEP_ORDER[0])
|
|
||||||
}, [applyStepSideEffects, scrollToCurrentTarget])
|
|
||||||
|
|
||||||
const finishTour = useCallback(() => {
|
const finishTour = useCallback(() => {
|
||||||
const userId = localStorage.getItem('active_userid')
|
const userId = localStorage.getItem('active_userid')
|
||||||
@@ -130,24 +128,26 @@ export function AppTourProvider({ children }: { children: ReactNode }) {
|
|||||||
const skipTour = finishTour
|
const skipTour = finishTour
|
||||||
|
|
||||||
const nextStep = useCallback(() => {
|
const nextStep = useCallback(() => {
|
||||||
const nextIndex = stepIndex + 1
|
setStepIndex((current) => {
|
||||||
if (nextIndex >= STEP_ORDER.length) {
|
if (current + 1 >= STEP_ORDER.length) {
|
||||||
finishTour()
|
finishTour()
|
||||||
return
|
return current
|
||||||
}
|
}
|
||||||
const nextId = STEP_ORDER[nextIndex]
|
return current + 1
|
||||||
setStepIndex(nextIndex)
|
})
|
||||||
applyStepSideEffects(nextId)
|
}, [finishTour])
|
||||||
scrollToCurrentTarget(nextId)
|
|
||||||
}, [applyStepSideEffects, finishTour, scrollToCurrentTarget, stepIndex])
|
|
||||||
|
|
||||||
const prevStep = useCallback(() => {
|
const prevStep = useCallback(() => {
|
||||||
const prevIndex = Math.max(0, stepIndex - 1)
|
setStepIndex((current) => Math.max(0, current - 1))
|
||||||
const prevId = STEP_ORDER[prevIndex]
|
}, [])
|
||||||
setStepIndex(prevIndex)
|
|
||||||
applyStepSideEffects(prevId)
|
useEffect(() => {
|
||||||
scrollToCurrentTarget(prevId)
|
if (!isActive) return
|
||||||
}, [applyStepSideEffects, scrollToCurrentTarget, stepIndex])
|
const stepId = STEP_ORDER[stepIndex]
|
||||||
|
if (!stepId) return
|
||||||
|
applyStepSideEffects(stepId)
|
||||||
|
scrollToCurrentTarget(stepId)
|
||||||
|
}, [isActive, stepIndex, applyStepSideEffects, scrollToCurrentTarget])
|
||||||
|
|
||||||
const restartTour = useCallback(() => {
|
const restartTour = useCallback(() => {
|
||||||
const userId = localStorage.getItem('active_userid')
|
const userId = localStorage.getItem('active_userid')
|
||||||
|
|||||||
Reference in New Issue
Block a user