Skip to content

Navigation & Routing

Shaker uses Expo Router 6 for file-based navigation, providing a type-safe, intuitive routing system. The app features a tab-based navigation structure with nested stack navigators for each major section.

The app has 5 main tabs (plus a hidden Venues tab):

┌─────────────────────────────────────────────────────────┐
│ Tab Bar │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│Dashboard │ Events │ Recipes │Inventory │ Settings │
└──────────┴──────────┴──────────┴──────────┴─────────────┘

Tab Configuration:

  • Dashboard (index.tsx) - Overview with quick actions and stats
  • Events (events/) - Event planning and management
  • Recipes (recipes/) - Recipe browsing and creation
  • Inventory (inventory/) - Stock tracking and management
  • Settings (settings/) - App configuration
  • Venues (venues/) - Venue management (hidden from tab bar)
app/
├── _layout.tsx # Root layout – fonts, splash, keep-awake, hydration
├── modal.tsx # Modal screen
├── +not-found.tsx # 404 screen
├── terms/
│ ├── PrivacyPolicy.tsx # Privacy policy
│ └── TermsOfService.tsx # Terms of service
└── (tabs)/ # Tab navigator
├── _layout.tsx # Tab bar configuration
├── index.tsx # Dashboard tab
├── events/ # Events stack
│ ├── _layout.tsx # Stack navigator
│ ├── index.tsx # Events list
│ ├── [id].tsx # Event detail
│ ├── add-event.tsx # Create event
│ ├── edit-event.tsx # Edit event
│ ├── guests.tsx # Guest list
│ ├── edit-guest.tsx # Edit guest details
│ ├── menu.tsx # Event menu view
│ ├── create-menu.tsx # Create/edit menu
│ ├── bar-setup.tsx # Bar setup view
│ ├── add-bar-setup.tsx # Add bar setup
│ ├── edit-bar-setup.tsx # Edit bar setup
│ └── batch-management.tsx # Event batch cocktail management
├── recipes/ # Recipes stack
│ ├── _layout.tsx # Stack navigator
│ ├── index.tsx # Recipe list
│ ├── [id].tsx # Recipe detail
│ ├── add-recipe.tsx # Create recipe
│ ├── edit-recipe.tsx # Edit recipe
│ ├── batch/ # Batch cocktails sub-stack
│ │ ├── index.tsx # Batch cocktails list
│ │ ├── create.tsx # Create batch
│ │ └── [id].tsx # Batch detail / tracker
│ └── make/ # Make-a-recipe guided flow
│ └── [id]/
│ ├── index.tsx # Step 1 – ingredient availability check
│ ├── select-instances.tsx # Step 2 – choose product instances
│ ├── steps.tsx # Step 3 – guided step-by-step making
│ └── complete.tsx # Step 4 – completion + inventory deduction
├── inventory/ # Inventory stack
│ ├── _layout.tsx # Stack navigator
│ ├── index.tsx # Inventory overview
│ ├── [id].tsx # Inventory item detail
│ ├── add-product.tsx # Add product (with barcode scan)
│ ├── edit-item.tsx # Edit inventory item
│ ├── inventory-items.tsx # All inventory items view
│ ├── view-all.tsx # View all items (flattened)
│ ├── quick-update.tsx # Quick level update screen
│ ├── cost-analytics.tsx # Cost analytics dashboard
│ ├── export.tsx # Export inventory data (JSON/CSV)
│ ├── import.tsx # Import inventory data
│ ├── products/ # Products sub-stack
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Products list
│ │ ├── [id].tsx # Product detail
│ │ ├── edit-product.tsx # Edit product
│ │ └── bulk-mapping.tsx # Bulk ingredient-to-product mapping
│ ├── ingredients/ # Ingredients sub-stack
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Ingredients list
│ │ ├── [id].tsx # Ingredient detail
│ │ ├── add-ingredient.tsx # Add ingredient
│ │ └── edit-ingredient.tsx # Edit ingredient
│ ├── equipment/ # Equipment sub-stack
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Equipment list
│ │ └── add-equipment.tsx # Add equipment
│ ├── glassware/ # Glassware sub-stack
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Glassware list
│ │ └── add-glassware.tsx # Add glassware
│ ├── tools/ # Tools sub-stack
│ │ ├── _layout.tsx
│ │ ├── index.tsx # Tools list
│ │ └── add-tool.tsx # Add tool
│ └── shopping/ # Shopping list sub-stack
│ ├── _layout.tsx
│ ├── index.tsx # Shopping list
│ ├── add-item.tsx # Add shopping list item
│ └── edit-item/[id].tsx # Edit shopping list item
├── settings/ # Settings stack
│ ├── _layout.tsx # Stack navigator
│ ├── index.tsx # Settings screen
│ └── user-profile.tsx # User profile editor
└── venues/ # Venues stack (hidden from tab bar)
├── _layout.tsx # Stack navigator
├── index.tsx # Venues list
├── [id].tsx # Venue detail
└── add-venue.tsx # Create venue

The tab bar uses the RetroUI design system with bold, distinctive styling:

<augment_code_snippet path=“shaker/app/(tabs)/_layout.tsx” mode=“EXCERPT”>

screenOptions={{
// Tab bar colors - NeoBrutalism design
tabBarActiveTintColor: designTokens.colors.gray[100],
tabBarInactiveTintColor: designTokens.colors.black,
// Tab bar styling - RetroUI theme
tabBarStyle: {
backgroundColor: designTokens.colors.teal.DEFAULT,
borderTopWidth: 6,
borderTopColor: designTokens.colors.black,
height: 100,
},
// ...
}}

</augment_code_snippet>

Key Features:

  • Teal background (#14B8A6)
  • 6px black top border
  • 100px height for comfortable touch targets
  • Bold typography (11px, bold weight)
  • Active state: White text
  • Inactive state: Black text

Each tab uses FontAwesome icons from the custom Icon component:

TabIconPurpose
DashboardlistOverview and quick actions
EventscalendarEvent planning
Recipeswhiskey-glassRecipe management
InventoryboxStock tracking
SettingsgearApp configuration

Each tab contains a stack navigator for hierarchical navigation within that section.

All stack navigators use consistent NeoBrutalism header styling:

screenOptions={{
headerStyle: {
backgroundColor: designTokens.colors.teal.DEFAULT,
height: 116,
borderBottomWidth: 6,
borderBottomColor: designTokens.colors.black,
},
headerTitleStyle: {
fontWeight: designTokens.typography.fontWeight.bold,
fontSize: designTokens.typography.fontSize.xl,
color: designTokens.colors.black,
},
headerTintColor: designTokens.colors.black,
headerShadowVisible: false,
}}

Features:

  • Teal background matching tab bar
  • 116px height
  • 6px black bottom border
  • Bold black title text
  • No shadow (solid design)
import { router } from 'expo-router';
// Navigate to a screen
router.push('/recipes/add-recipe');
// Navigate with parameters
router.push(`/recipes/${recipeId}`);
// Go back
router.back();
// Replace current screen
router.replace('/inventory');

Expo Router provides type-safe navigation with TypeScript:

import { Href } from 'expo-router';
const recipeDetailRoute: Href = `/recipes/${recipeId}`;
router.push(recipeDetailRoute);

Modal screens are defined at the root level:

app/modal.tsx
export default function Modal() {
return (
<View>
<Text>Modal Content</Text>
</View>
);
}
// Navigate to modal
router.push('/modal');

The app uses the shaker:// URL scheme for deep linking:

shaker://recipes/123 # Open recipe detail
shaker://inventory/products # Open products list
shaker://events/add-event # Open create event screen

Deep linking is configured in app.json:

{
"expo": {
"scheme": "shaker"
}
}

Always use TypeScript for route parameters:

import { useLocalSearchParams } from 'expo-router';
export default function RecipeDetail() {
const { id } = useLocalSearchParams<{ id: string }>();
// ...
}

Use navigation state for conditional rendering:

import { useNavigation } from 'expo-router';
const navigation = useNavigation();
const canGoBack = navigation.canGoBack();

Use headerShown: false for tabs with stack navigators to avoid double headers.

Handle Android back button appropriately:

import { useRouter } from 'expo-router';
import { BackHandler } from 'react-native';
useEffect(() => {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
() => {
router.back();
return true;
}
);
return () => backHandler.remove();
}, []);

The “Make a Recipe” feature is a guided 4-step flow under recipes/make/[id]/. It walks the user through checking inventory, selecting product instances, following instructions step by step, and recording consumption.

recipes/make/[id]/
├── index.tsx # Step 1: Ingredient availability check
│ # Shows in-stock / low / missing per ingredient
│ # Option to add missing items to shopping list
├── select-instances.tsx # Step 2: Choose product instances
│ # For ingredients with multiple products, user picks which bottle
├── steps.tsx # Step 3: Guided step-by-step making mode
│ # Alternates between ingredient steps and instruction steps
│ # Timer support for timed instructions
│ # Check-off for each ingredient as poured
└── complete.tsx # Step 4: Completion summary
# Shows what was deducted from inventory
# Highlights any products now low/empty

Navigation entry point:

router.push(`/recipes/make/${recipeId}`);

State between steps:

Step 3 → Step 4 passes completion state via store/makeCompletionStore.ts (an in-memory store) to avoid URL length limits:

import { setMakeCompletion, getMakeCompletion } from '@/store/makeCompletionStore';
// In steps.tsx – after applying consumption
setMakeCompletion({ recipeId, recipeName, deductions, lowStockAfter });
router.replace(`/recipes/make/${id}/complete`);
// In complete.tsx
const completion = getMakeCompletion();

Batch cocktails are pre-made, large-quantity preparations tracked across events. The flow lives at recipes/batch/.

recipes/batch/
├── index.tsx # List of all batched cocktails with summary stats and low-stock alerts
├── create.tsx # Create a new batch (select recipe, set serving count, storage location)
└── [id].tsx # Batch detail – status, servings tracker, storage info

Batch status values: preparingreadyservingdepleted / expired

Event-specific batch management is accessible from the Events tab at events/batch-management.tsx.

The Inventory tab contains five independently navigable sub-sections, each with their own stack layout:

Sub-sectionRoute prefixDescription
Products/inventory/products/Physical bar products with barcode/UPC support
Ingredients/inventory/ingredients/Abstract ingredient management
Equipment/inventory/equipment/Bar equipment (shakers, strainers, etc.)
Glassware/inventory/glassware/Glassware collection
Tools/inventory/tools/Bar tools (jiggers, muddlers, etc.)

Shopping lists are embedded in the inventory section at /inventory/shopping/.

ScreenRouteDescription
Quick Update/inventory/quick-updateRapidly update product levels
Cost Analytics/inventory/cost-analyticsTotal value, spending, price trends
Export Data/inventory/exportExport products/ingredients/shopping as JSON or CSV
Import Data/inventory/importImport inventory from exported files
View All/inventory/view-allFlat view of all inventory items

The Settings tab has two screens:

settings/
├── index.tsx # App settings (preferences, data management, legal links)
└── user-profile.tsx # User profile editor (name, email, phone with validation)

app/_layout.tsx orchestrates the startup sequence before the tab navigator mounts:

  1. Font loading – SpaceMono + FontAwesome icon fonts
  2. Local storage hydration – Calls hydrateFromLocalStorage() which migrates AsyncStorage → MMKV on first run, then loads all domain data into memory
  3. Splash screen – Hidden after hydration completes
  4. Keep-awake – Activates expo-keep-awake on native platforms (screen stays on during events/bar service)

2026-03-06