Skip to content

Navigation

The app uses Expo Router for file-based navigation with route groups.

Route Structure

app/
├── _layout.tsx           # Root layout
├── (auth)/
│   ├── _layout.tsx       # Auth stack
│   ├── welcome.tsx       # Landing screen
│   ├── create-account.tsx
│   ├── check-email.tsx
│   ├── device-selection.tsx
│   ├── genres.tsx
│   ├── goal.tsx
│   └── success.tsx
└── (tabs)/
    ├── _layout.tsx       # Tab navigator
    ├── index.tsx         # Home tab
    ├── discover.tsx      # Discover tab
    ├── create.tsx        # Create tab
    ├── clubs.tsx         # Clubs tab
    └── profile.tsx       # Profile tab

Auth Flow

The onboarding flow progresses through these screens:

  1. welcome - App introduction
  2. create-account - Email input
  3. check-email - Magic link sent
  4. device-selection - Reading device preferences
  5. genres - Genre selection
  6. goal - Reading goal setting
  7. success - Welcome to the app

Programmatic Navigation

typescript
import { router } from "expo-router"

// Navigate to next onboarding step
router.push("/check-email")

// Replace (no back button)
router.replace("/success")

// Navigate to authenticated area
router.replace("/(tabs)")

Tab Navigation

Five-tab bottom navigation:

TabScreenIcon
Homeindex.tsxHouse
Discoverdiscover.tsxSearch
Createcreate.tsxPlus
Clubsclubs.tsxUsers
Profileprofile.tsxUser

Tab Configuration

typescript
// app/(tabs)/_layout.tsx
<Tabs>
	<Tabs.Screen
		name="index"
		options={{
			title: "Home",
			tabBarIcon: ({ color }) => <Home color={color} />,
		}}
	/>
	{/* ... other tabs */}
</Tabs>

Deep Linking

The app supports deep links via the logbook:// scheme:

typescript
// Handle magic link verification
logbook://verify?token=xxx

// Navigate to specific book
logbook://books/123

Configuration

Deep linking is configured in app.json:

json
{
  "expo": {
    "scheme": "logbook",
    "ios": {
      "associatedDomains": ["applinks:logbook.so"]
    }
  }
}

Protected Routes

Routes are protected by the AuthGate in _layout.tsx:

typescript
const { isAuthenticated, isLoading } = useAuthStore()

if (isLoading) {
	return <SplashScreen />
}

// Redirect based on auth state
useEffect(() => {
	if (!isAuthenticated) {
		router.replace("/(auth)/welcome")
	}
}, [isAuthenticated])

Built with VitePress