diff --git a/backend/__pycache__/models.cpython-312.pyc b/backend/__pycache__/models.cpython-312.pyc index bff9712..1b1cfe7 100644 Binary files a/backend/__pycache__/models.cpython-312.pyc and b/backend/__pycache__/models.cpython-312.pyc differ diff --git a/backend/models.py b/backend/models.py index 2908e2f..02529e2 100644 --- a/backend/models.py +++ b/backend/models.py @@ -38,6 +38,7 @@ class UserUpdate(BaseModel): displayName: Optional[str] = None photoURL: Optional[str] = None theme: Optional[str] = None + tutorial: Optional[bool] = None class Config: json_schema_extra = { @@ -56,6 +57,7 @@ class User(BaseModel): createdAt: datetime updatedAt: datetime theme: str = "light" + tutorial: Optional[bool] = None class Config: from_attributes = True diff --git a/backend/routers/users.py b/backend/routers/users.py index 7240909..c6a6d3b 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -79,6 +79,7 @@ async def get_user_by_email(email: str): "displayName": user.get("displayName"), "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), + "tutorial": user.get("tutorial"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat() } @@ -151,6 +152,7 @@ async def update_user(user_id: str, user_data: UserUpdate): "displayName": user.get("displayName"), "photoURL": user.get("photoURL"), "theme": user.get("theme", "light"), + "tutorial": user.get("tutorial"), "createdAt": user["createdAt"].isoformat(), "updatedAt": user["updatedAt"].isoformat(), "message": "User updated successfully" diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 2cb0ccb..0d94513 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -37,6 +37,7 @@ type MongoUser = { displayName?: string photoURL?: string theme?: string + tutorial?: boolean } type AuthContextValue = { @@ -211,8 +212,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { // Clear secret key from memory setSecretKey(null) setMongoUser(null) - // Reset onboarding so tour shows again on next login - localStorage.removeItem('gj-onboarding-done') + // Clear pending tour step (session state) localStorage.removeItem('gj-tour-pending-step') // Keep device key and encrypted key for next login // Do NOT clear localStorage or IndexedDB diff --git a/src/hooks/useOnboardingTour.ts b/src/hooks/useOnboardingTour.ts index f0ccad0..17a17e6 100644 --- a/src/hooks/useOnboardingTour.ts +++ b/src/hooks/useOnboardingTour.ts @@ -3,17 +3,8 @@ import { useNavigate } from 'react-router-dom' import { driver, type DriveStep } from 'driver.js' import 'driver.js/dist/driver.css' -const ONBOARDING_KEY = 'gj-onboarding-done' 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 { return localStorage.getItem(TOUR_PENDING_KEY) } @@ -144,7 +135,6 @@ export function useOnboardingTour() { const driverObj = driver({ ...driverDefaults(), onDestroyStarted: () => { - markOnboardingDone() clearPendingTourStep() driverObj.destroy() }, @@ -175,7 +165,6 @@ export function useOnboardingTour() { const driverObj = driver({ ...driverDefaults(), onDestroyStarted: () => { - markOnboardingDone() clearPendingTourStep() driverObj.destroy() }, @@ -206,7 +195,6 @@ export function useOnboardingTour() { const driverObj = driver({ ...driverDefaults(), onDestroyStarted: () => { - markOnboardingDone() clearPendingTourStep() driverObj.destroy() }, @@ -216,7 +204,6 @@ export function useOnboardingTour() { // Last settings step → navigate to / if (activeIndex === steps.length - 1) { - markOnboardingDone() clearPendingTourStep() driverObj.destroy() navigate('/') diff --git a/src/lib/api.ts b/src/lib/api.ts index 43a24ba..c31e139 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -70,7 +70,7 @@ export async function getUserByEmail(email: string, token: string) { export async function updateUserProfile( userId: string, - updates: { displayName?: string; photoURL?: string; theme?: string }, + updates: { displayName?: string; photoURL?: string; theme?: string; tutorial?: boolean }, token: string ) { return apiCall(`/users/${userId}`, { diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 8b331fa..8f1b651 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,12 +1,12 @@ import { useAuth } from '../contexts/AuthContext' import { Link, useNavigate } from 'react-router-dom' import { useState, useRef, useEffect } from 'react' -import { createEntry } from '../lib/api' +import { createEntry, updateUserProfile } from '../lib/api' import { encryptEntry } from '../lib/crypto' import BottomNav from '../components/BottomNav' import WelcomeModal from '../components/WelcomeModal' import { SaveBookAnimation } from '../components/SaveBookAnimation' -import { useOnboardingTour, hasSeenOnboarding, markOnboardingDone } from '../hooks/useOnboardingTour' +import { useOnboardingTour } from '../hooks/useOnboardingTour' import { PageLoader } from '../components/PageLoader' const AFFIRMATIONS = [ @@ -31,7 +31,7 @@ const SAVE_LEAVES = [ ] export default function HomePage() { - const { user, userId, secretKey, loading } = useAuth() + const { user, userId, mongoUser, secretKey, loading } = useAuth() const navigate = useNavigate() const [entry, setEntry] = useState('') const [title, setTitle] = useState('') @@ -47,19 +47,26 @@ export default function HomePage() { // Check if onboarding should be shown after login useEffect(() => { - if (!loading && user && userId && !hasSeenOnboarding()) { + if (!loading && user && userId && mongoUser && !mongoUser.tutorial) { 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 = () => { setShowWelcome(false) + markTutorialDone() startTour() } const handleSkipTour = () => { setShowWelcome(false) - markOnboardingDone() + markTutorialDone() } if (loading) { diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 7324662..1ebbef3 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -28,7 +28,7 @@ export default function LoginPage() { } } - if (loading) { + if (loading || signingIn) { return }