This commit is contained in:
2026-03-31 10:23:49 +05:30
parent f488400c6d
commit cfecfa5116
8 changed files with 21 additions and 23 deletions

View File

@@ -38,6 +38,7 @@ class UserUpdate(BaseModel):
displayName: Optional[str] = None displayName: Optional[str] = None
photoURL: Optional[str] = None photoURL: Optional[str] = None
theme: Optional[str] = None theme: Optional[str] = None
tutorial: Optional[bool] = None
class Config: class Config:
json_schema_extra = { json_schema_extra = {
@@ -56,6 +57,7 @@ class User(BaseModel):
createdAt: datetime createdAt: datetime
updatedAt: datetime updatedAt: datetime
theme: str = "light" theme: str = "light"
tutorial: Optional[bool] = None
class Config: class Config:
from_attributes = True from_attributes = True

View File

@@ -79,6 +79,7 @@ async def get_user_by_email(email: str):
"displayName": user.get("displayName"), "displayName": user.get("displayName"),
"photoURL": user.get("photoURL"), "photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"), "theme": user.get("theme", "light"),
"tutorial": user.get("tutorial"),
"createdAt": user["createdAt"].isoformat(), "createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat() "updatedAt": user["updatedAt"].isoformat()
} }
@@ -151,6 +152,7 @@ async def update_user(user_id: str, user_data: UserUpdate):
"displayName": user.get("displayName"), "displayName": user.get("displayName"),
"photoURL": user.get("photoURL"), "photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"), "theme": user.get("theme", "light"),
"tutorial": user.get("tutorial"),
"createdAt": user["createdAt"].isoformat(), "createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat(),
"message": "User updated successfully" "message": "User updated successfully"

View File

@@ -37,6 +37,7 @@ type MongoUser = {
displayName?: string displayName?: string
photoURL?: string photoURL?: string
theme?: string theme?: string
tutorial?: boolean
} }
type AuthContextValue = { type AuthContextValue = {
@@ -211,8 +212,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// Clear secret key from memory // Clear secret key from memory
setSecretKey(null) setSecretKey(null)
setMongoUser(null) setMongoUser(null)
// Reset onboarding so tour shows again on next login // Clear pending tour step (session state)
localStorage.removeItem('gj-onboarding-done')
localStorage.removeItem('gj-tour-pending-step') localStorage.removeItem('gj-tour-pending-step')
// Keep device key and encrypted key for next login // Keep device key and encrypted key for next login
// Do NOT clear localStorage or IndexedDB // Do NOT clear localStorage or IndexedDB

View File

@@ -3,17 +3,8 @@ import { useNavigate } from 'react-router-dom'
import { driver, type DriveStep } from 'driver.js' import { driver, type DriveStep } from 'driver.js'
import 'driver.js/dist/driver.css' import 'driver.js/dist/driver.css'
const ONBOARDING_KEY = 'gj-onboarding-done'
const TOUR_PENDING_KEY = 'gj-tour-pending-step' const TOUR_PENDING_KEY = 'gj-tour-pending-step'
export function hasSeenOnboarding(): boolean {
return localStorage.getItem(ONBOARDING_KEY) === 'true'
}
export function markOnboardingDone(): void {
localStorage.setItem(ONBOARDING_KEY, 'true')
}
export function hasPendingTourStep(): string | null { export function hasPendingTourStep(): string | null {
return localStorage.getItem(TOUR_PENDING_KEY) return localStorage.getItem(TOUR_PENDING_KEY)
} }
@@ -144,7 +135,6 @@ export function useOnboardingTour() {
const driverObj = driver({ const driverObj = driver({
...driverDefaults(), ...driverDefaults(),
onDestroyStarted: () => { onDestroyStarted: () => {
markOnboardingDone()
clearPendingTourStep() clearPendingTourStep()
driverObj.destroy() driverObj.destroy()
}, },
@@ -175,7 +165,6 @@ export function useOnboardingTour() {
const driverObj = driver({ const driverObj = driver({
...driverDefaults(), ...driverDefaults(),
onDestroyStarted: () => { onDestroyStarted: () => {
markOnboardingDone()
clearPendingTourStep() clearPendingTourStep()
driverObj.destroy() driverObj.destroy()
}, },
@@ -206,7 +195,6 @@ export function useOnboardingTour() {
const driverObj = driver({ const driverObj = driver({
...driverDefaults(), ...driverDefaults(),
onDestroyStarted: () => { onDestroyStarted: () => {
markOnboardingDone()
clearPendingTourStep() clearPendingTourStep()
driverObj.destroy() driverObj.destroy()
}, },
@@ -216,7 +204,6 @@ export function useOnboardingTour() {
// Last settings step → navigate to / // Last settings step → navigate to /
if (activeIndex === steps.length - 1) { if (activeIndex === steps.length - 1) {
markOnboardingDone()
clearPendingTourStep() clearPendingTourStep()
driverObj.destroy() driverObj.destroy()
navigate('/') navigate('/')

View File

@@ -70,7 +70,7 @@ export async function getUserByEmail(email: string, token: string) {
export async function updateUserProfile( export async function updateUserProfile(
userId: string, userId: string,
updates: { displayName?: string; photoURL?: string; theme?: string }, updates: { displayName?: string; photoURL?: string; theme?: string; tutorial?: boolean },
token: string token: string
) { ) {
return apiCall(`/users/${userId}`, { return apiCall(`/users/${userId}`, {

View File

@@ -1,12 +1,12 @@
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import { createEntry } from '../lib/api' import { createEntry, updateUserProfile } from '../lib/api'
import { encryptEntry } from '../lib/crypto' import { encryptEntry } from '../lib/crypto'
import BottomNav from '../components/BottomNav' import BottomNav from '../components/BottomNav'
import WelcomeModal from '../components/WelcomeModal' import WelcomeModal from '../components/WelcomeModal'
import { SaveBookAnimation } from '../components/SaveBookAnimation' import { SaveBookAnimation } from '../components/SaveBookAnimation'
import { useOnboardingTour, hasSeenOnboarding, markOnboardingDone } from '../hooks/useOnboardingTour' import { useOnboardingTour } from '../hooks/useOnboardingTour'
import { PageLoader } from '../components/PageLoader' import { PageLoader } from '../components/PageLoader'
const AFFIRMATIONS = [ const AFFIRMATIONS = [
@@ -31,7 +31,7 @@ const SAVE_LEAVES = [
] ]
export default function HomePage() { export default function HomePage() {
const { user, userId, secretKey, loading } = useAuth() const { user, userId, mongoUser, secretKey, loading } = useAuth()
const navigate = useNavigate() const navigate = useNavigate()
const [entry, setEntry] = useState('') const [entry, setEntry] = useState('')
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
@@ -47,19 +47,26 @@ export default function HomePage() {
// Check if onboarding should be shown after login // Check if onboarding should be shown after login
useEffect(() => { useEffect(() => {
if (!loading && user && userId && !hasSeenOnboarding()) { if (!loading && user && userId && mongoUser && !mongoUser.tutorial) {
setShowWelcome(true) setShowWelcome(true)
} }
}, [loading, user, userId]) }, [loading, user, userId, mongoUser])
async function markTutorialDone() {
if (!user || !userId) return
const token = await user.getIdToken()
updateUserProfile(userId, { tutorial: true }, token).catch(console.error)
}
const handleStartTour = () => { const handleStartTour = () => {
setShowWelcome(false) setShowWelcome(false)
markTutorialDone()
startTour() startTour()
} }
const handleSkipTour = () => { const handleSkipTour = () => {
setShowWelcome(false) setShowWelcome(false)
markOnboardingDone() markTutorialDone()
} }
if (loading) { if (loading) {

View File

@@ -28,7 +28,7 @@ export default function LoginPage() {
} }
} }
if (loading) { if (loading || signingIn) {
return <PageLoader /> return <PageLoader />
} }