seo update
This commit is contained in:
@@ -2917,11 +2917,17 @@
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
@keyframes tour-btn-pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.6; transform: scale(0.96); }
|
||||
}
|
||||
|
||||
.gj-tour-popover .driver-popover-next-btn {
|
||||
background: var(--color-primary, #22c55e) !important;
|
||||
color: #fff !important;
|
||||
border: none !important;
|
||||
text-shadow: none !important;
|
||||
animation: tour-btn-pulse 1.2s ease-in-out infinite !important;
|
||||
}
|
||||
|
||||
.gj-tour-popover .driver-popover-prev-btn {
|
||||
|
||||
@@ -12,8 +12,9 @@ function App() {
|
||||
<AuthProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<LoginPage />} />
|
||||
<Route
|
||||
path="/"
|
||||
path="/write"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<HomePage />
|
||||
@@ -36,8 +37,7 @@ function App() {
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -21,8 +21,8 @@ export default function BottomNav() {
|
||||
{/* Write */}
|
||||
<button
|
||||
type="button"
|
||||
className={`bottom-nav-btn ${isActive('/') ? 'bottom-nav-btn-active' : ''}`}
|
||||
onClick={() => navigate('/')}
|
||||
className={`bottom-nav-btn ${isActive('/write') ? 'bottom-nav-btn-active' : ''}`}
|
||||
onClick={() => navigate('/write')}
|
||||
aria-label="Write"
|
||||
>
|
||||
{/* Pencil / edit icon */}
|
||||
|
||||
@@ -16,7 +16,7 @@ export function ProtectedRoute({ children }: Props) {
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />
|
||||
return <Navigate to="/" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { driver, type DriveStep } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
@@ -128,14 +128,17 @@ function getSettingsSteps(isMobile: boolean): DriveStep[] {
|
||||
export function useOnboardingTour() {
|
||||
const navigate = useNavigate()
|
||||
const driverRef = useRef<ReturnType<typeof driver> | null>(null)
|
||||
const [isTourActive, setIsTourActive] = useState(false)
|
||||
|
||||
const startTour = useCallback(() => {
|
||||
const isMobile = window.innerWidth < 860
|
||||
setIsTourActive(true)
|
||||
|
||||
const driverObj = driver({
|
||||
...driverDefaults(),
|
||||
onDestroyStarted: () => {
|
||||
clearPendingTourStep()
|
||||
setIsTourActive(false)
|
||||
driverObj.destroy()
|
||||
},
|
||||
onNextClick: () => {
|
||||
@@ -145,6 +148,7 @@ export function useOnboardingTour() {
|
||||
// Last home step → navigate to /history
|
||||
if (activeIndex === steps.length - 1) {
|
||||
localStorage.setItem(TOUR_PENDING_KEY, 'history')
|
||||
setIsTourActive(false)
|
||||
driverObj.destroy()
|
||||
navigate('/history')
|
||||
return
|
||||
@@ -206,7 +210,7 @@ export function useOnboardingTour() {
|
||||
if (activeIndex === steps.length - 1) {
|
||||
clearPendingTourStep()
|
||||
driverObj.destroy()
|
||||
navigate('/')
|
||||
navigate('/write')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,5 +223,5 @@ export function useOnboardingTour() {
|
||||
setTimeout(() => driverObj.drive(), 300)
|
||||
}, [navigate])
|
||||
|
||||
return { startTour, continueTourOnHistory, continueTourOnSettings }
|
||||
return { startTour, continueTourOnHistory, continueTourOnSettings, isTourActive }
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function HomePage() {
|
||||
const titleInputRef = useRef<HTMLInputElement>(null)
|
||||
const contentTextareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const { startTour } = useOnboardingTour()
|
||||
const { startTour, isTourActive } = useOnboardingTour()
|
||||
|
||||
// Check if onboarding should be shown after login
|
||||
useEffect(() => {
|
||||
@@ -52,6 +52,14 @@ export default function HomePage() {
|
||||
}
|
||||
}, [loading, user, userId, mongoUser])
|
||||
|
||||
// On-demand tour triggered from Settings (no DB read/write)
|
||||
useEffect(() => {
|
||||
if (!loading && user && localStorage.getItem('gj-force-tour') === 'true') {
|
||||
localStorage.removeItem('gj-force-tour')
|
||||
setTimeout(() => startTour(), 150)
|
||||
}
|
||||
}, [loading, user, startTour])
|
||||
|
||||
async function markTutorialDone() {
|
||||
if (!user || !userId) return
|
||||
const token = await user.getIdToken()
|
||||
@@ -78,7 +86,7 @@ export default function HomePage() {
|
||||
<div className="home-page" style={{ alignItems: 'center', justifyContent: 'center', gap: '1rem' }}>
|
||||
<h1 style={{ fontFamily: '"Sniglet", system-ui', color: 'var(--color-text)' }}>Grateful Journal</h1>
|
||||
<p style={{ color: 'var(--color-text-muted)' }}>Sign in to start your journal.</p>
|
||||
<Link to="/login" className="home-login-link">Go to login</Link>
|
||||
<Link to="/" className="home-login-link">Go to login</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -178,7 +186,7 @@ export default function HomePage() {
|
||||
onKeyDown={handleTitleKeyDown}
|
||||
enterKeyHint="next"
|
||||
ref={titleInputRef}
|
||||
disabled={phase !== 'idle'}
|
||||
disabled={phase !== 'idle' || isTourActive}
|
||||
/>
|
||||
<textarea
|
||||
id="tour-content-textarea"
|
||||
@@ -188,7 +196,7 @@ export default function HomePage() {
|
||||
onChange={(e) => setEntry(e.target.value)}
|
||||
enterKeyHint="enter"
|
||||
ref={contentTextareaRef}
|
||||
disabled={phase !== 'idle'}
|
||||
disabled={phase !== 'idle' || isTourActive}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -227,7 +235,7 @@ export default function HomePage() {
|
||||
id="tour-save-btn"
|
||||
className="journal-write-btn"
|
||||
onClick={handleWrite}
|
||||
disabled={phase !== 'idle' || !title.trim() || !entry.trim()}
|
||||
disabled={phase !== 'idle' || isTourActive || !title.trim() || !entry.trim()}
|
||||
>
|
||||
{phase === 'saving' ? 'Saving...' : 'Save Entry'}
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function LoginPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return
|
||||
if (user) navigate('/', { replace: true })
|
||||
if (user) navigate('/write', { replace: true })
|
||||
}, [user, loading, navigate])
|
||||
|
||||
async function handleGoogleSignIn() {
|
||||
|
||||
@@ -71,9 +71,8 @@ export default function SettingsPage() {
|
||||
}, [])
|
||||
|
||||
const handleSeeTutorial = () => {
|
||||
localStorage.removeItem('gj-onboarding-done')
|
||||
localStorage.removeItem('gj-tour-pending-step')
|
||||
navigate('/')
|
||||
localStorage.setItem('gj-force-tour', 'true')
|
||||
navigate('/write')
|
||||
}
|
||||
|
||||
const displayName = mongoUser?.displayName || user?.displayName || 'User'
|
||||
|
||||
Reference in New Issue
Block a user