Files
Sage/.planning/phases/02-household-creation/02-06-PLAN.md
Dani B e20858f608 docs(02): create phase 2 household creation plans
Phase 2: Household Creation & Invites
- 6 plans in 4 waves covering SHARE-01 through SHARE-05
- Data models, repository pattern, and Supabase integration
- Database schema with RLS policies for multi-tenant isolation
- State management with Riverpod and business logic use cases
- Complete UI components for household management
- Navigation integration with authentication flow

Ready for execution: /gsd:execute-phase 2
2026-01-28 15:50:09 -05:00

24 KiB

phase: 02-household-creation plan: 06 type: execute wave: 4 depends_on: [02-05, 02-03] files_modified: - lib/core/router/app_router.dart - lib/features/home/presentation/pages/home_page.dart - lib/providers/auth_provider.dart - lib/main.dart autonomous: true must_haves: truths: - "Navigation system integrates household selection into app flow" - "Auth state changes automatically load user households" - "Home page displays current household context with navigation to household management" - "Routing handles all household states (no household, single household, multiple households)" - "Real-time household updates reflect across the app" artifacts: - path: "lib/core/router/app_router.dart" provides: "Household-aware navigation routes" contains: "HouseholdListRoute", "CreateHouseholdRoute", "JoinHouseholdRoute" - path: "lib/features/home/presentation/pages/home_page.dart" provides: "Home page with household context" contains: "current household display", "switch household action" - path: "lib/providers/auth_provider.dart" provides: "Auth state with household integration" contains: "household loading on auth change" key_links: - from: "lib/core/router/app_router.dart" to: "lib/features/household/presentation/pages/household_list_page.dart" via: "route definition" pattern: "HouseholdListRoute.*HouseholdListPage" - from: "lib/providers/auth_provider.dart" to: "lib/features/household/providers/household_provider.dart" via: "state synchronization" pattern: "ref.read.*householdProvider.*loadUserHouseholds" Integrate household functionality into app navigation and authentication flow for seamless user experience.

Purpose: Connect household management with existing app architecture so users naturally transition from authentication to household setup and inventory management. Output: Fully integrated household system with navigation, auth integration, and state management ready for production.

<execution_context> @/.opencode/get-shit-done/workflows/execute-plan.md @/.opencode/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md

Navigation and Auth References

@lib/core/router/app_router.dart @lib/features/home/presentation/pages/home_page.dart @lib/providers/auth_provider.dart @lib/main.dart

Update Router with Household Routes lib/core/router/app_router.dart Add household routes to existing router structure:
1. Import household pages:
```dart
import '../features/household/presentation/pages/household_list_page.dart';
import '../features/household/presentation/pages/create_household_page.dart';
import '../features/household/presentation/pages/join_household_page.dart';
import '../features/household/providers/household_provider.dart';
import '../providers/auth_provider.dart';
import '../features/household/domain/entities/household_entity.dart';
```

2. Add household route definitions:
```dart
// Household routes
static const householdList = '/households';
static const createHousehold = '/households/create';
static const joinHousehold = '/households/join';
```

3. Update router with household routes:
```dart
GoRouter router({required String initialLocation}) {
  return GoRouter(
    initialLocation: initialLocation,
    redirect: (context, state) {
      final auth = ref.read(authProvider);
      
      // Handle unauthenticated users
      if (auth.user == null) {
        final isAuthRoute = state.location.startsWith('/auth') || 
                           state.location == '/';
        if (!isAuthRoute) {
          return '/auth/login';
        }
        return null;
      }
      
      // Authenticated user flow
      final user = auth.user!;
      
      // Check if user has households
      final householdsAsync = ref.read(householdProvider);
      final households = householdsAsync.maybeWhen(
        data: (households) => households,
        orElse: () => null,
      );
      
      // If households not loaded yet, don't redirect
      if (households == null) return null;
      
      // If no households, go to household list to create/join
      if (households.isEmpty) {
        final isHouseholdRoute = state.location.startsWith('/households');
        if (!isHouseholdRoute) {
          return HouseholdListRoute.householdList;
        }
        return null;
      }
      
      // Has households - check if current household is set
      final currentHousehold = ref.read(currentHouseholdProvider);
      if (currentHousehold == null) {
        // Auto-select first household
        ref.read(currentHouseholdProvider.notifier).state = households.first;
      }
      
      // Handle specific redirects
      if (state.location == '/' || state.location == '/auth/login') {
        return HomePage.route;
      }
      
      return null;
    },
    routes: [
      // Existing auth routes...
      
      // Household routes
      GoRoute(
        path: HouseholdListRoute.householdList,
        name: 'household-list',
        builder: (context, state) => const HouseholdListPage(),
      ),
      GoRoute(
        path: HouseholdListRoute.createHousehold,
        name: 'create-household',
        builder: (context, state) => const CreateHouseholdPage(),
      ),
      GoRoute(
        path: HouseholdListRoute.joinHousehold,
        name: 'join-household',
        builder: (context, state) => const JoinHouseholdPage(),
      ),
      
      // Home route (existing)
      GoRoute(
        path: HomePage.route,
        name: 'home',
        builder: (context, state) => const HomePage(),
      ),
    ],
    errorBuilder: (context, state) => Scaffold(
      body: Center(
        child: Text('Page not found: ${state.location}'),
      ),
    ),
  );
}
```

4. Add household navigation helpers:
```dart
class HouseholdListRoute {
  static const householdList = '/households';
  static const createHousehold = '/households/create';
  static const joinHousehold = '/households/join';
  
  static void goToList(BuildContext context) {
    GoRouter.of(context).push(householdList);
  }
  
  static void goToCreate(BuildContext context) {
    GoRouter.of(context).push(createHousehold);
  }
  
  static void goToJoin(BuildContext context) {
    GoRouter.of(context).push(joinHousehold);
  }
}
```

Update auth redirect logic to handle household selection.
Include proper route guards for authenticated users with no households.
flutter analyze lib/core/router/app_router.dart passes Router includes household routes with proper authentication and household state guards Update Auth Provider with Household Integration lib/providers/auth_provider.dart Add household loading to auth state changes:
1. Import household provider:
```dart
import '../features/household/providers/household_provider.dart';
import '../features/household/providers/household_provider.dart' as household;
```

2. Add household loading to auth state changes:
```dart
class AuthProvider extends StateNotifier<AsyncValue<AuthState>> {
  final AuthRepository _repository;
  
  AuthProvider(this._repository) : super(const AsyncValue.loading()) {
    _initialize();
  }
  
  Future<void> _initialize() async {
    state = const AsyncValue.loading();
    try {
      final currentUser = _repository.currentUser;
      if (currentUser == null) {
        state = const AsyncValue.data(AuthState.unauthenticated());
        return;
      }
      
      // User is authenticated, get their session
      final session = await _repository.getCurrentSession();
      if (session == null) {
        state = const AsyncValue.data(AuthState.unauthenticated());
        return;
      }
      
      state = AsyncValue.data(AuthState.authenticated(currentUser));
      
      // Load user households after successful auth
      _loadUserHouseholds(currentUser.id);
      
    } catch (e, stack) {
      state = AsyncValue.error(e, stack);
    }
  }
  
  Future<void> signIn(String email, String password) async {
    state = const AsyncValue.loading();
    try {
      final user = await _repository.signIn(email, password);
      state = AsyncValue.data(AuthState.authenticated(user));
      
      // Load households after successful sign in
      _loadUserHouseholds(user.id);
      
    } catch (e, stack) {
      state = AsyncValue.error(e, stack);
    }
  }
  
  Future<void> signUp(String email, String password) async {
    state = const AsyncValue.loading();
    try {
      final user = await _repository.signUp(email, password);
      state = AsyncValue.data(AuthState.authenticated(user));
      
      // Load households after successful sign up
      _loadUserHouseholds(user.id);
      
    } catch (e, stack) {
      state = AsyncValue.error(e, stack);
    }
  }
  
  Future<void> signOut() async {
    try {
      await _repository.signOut();
      state = const AsyncValue.data(AuthState.unauthenticated());
      
      // Clear household state
      household.clearHouseholds();
      ref.read(currentHouseholdProvider.notifier).state = null;
      
    } catch (e, stack) {
      state = AsyncValue.error(e, stack);
    }
  }
  
  Future<void> _loadUserHouseholds(String userId) async {
    try {
      // This will trigger the household provider to load
      final container = ProviderScope.containerOf(context!);
      final householdNotifier = container.read(household.householdProvider.notifier);
      await householdNotifier.loadUserHouseholds(userId);
    } catch (e) {
      // Household loading failure shouldn't break auth flow
      // The household provider will handle its own error states
    }
  }
}
```

3. Update auth state to include household info:
```dart
class AuthState {
  final User? user;
  final bool isAuthenticated;
  final bool hasHouseholds;
  
  const AuthState({
    this.user,
    required this.isAuthenticated,
    this.hasHouseholds = false,
  });
  
  factory AuthState.unauthenticated() => const AuthState(
    isAuthenticated: false,
  );
  
  factory AuthState.authenticated(User user, {bool hasHouseholds = false}) => AuthState(
    user: user,
    isAuthenticated: true,
    hasHouseholds: hasHouseholds,
  );
  
  AuthState copyWith({
    User? user,
    bool? isAuthenticated,
    bool? hasHouseholds,
  }) {
    return AuthState(
      user: user ?? this.user,
      isAuthenticated: isAuthenticated ?? this.isAuthenticated,
      hasHouseholds: hasHouseholds ?? this.hasHouseholds,
    );
  }
}
```

Ensure household state is synchronized with authentication changes.
Include proper cleanup on sign out.
flutter analyze lib/providers/auth_provider.dart passes Auth provider integrates household loading with authentication state changes Update Home Page with Household Context lib/features/home/presentation/pages/home_page.dart Add household context and navigation to home page:
1. Add imports:
```dart
import '../../household/providers/household_provider.dart';
import '../../household/presentation/pages/household_list_page.dart';
import '../widgets/household_switcher.dart'; // We'll create this widget
```

2. Add household context to home page:
```dart
class HomePage extends ConsumerWidget {
  const HomePage({Key? key}) : super(key: key);
  
  static const String route = '/home';
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(authProvider).user;
    final currentHousehold = ref.watch(currentHouseholdProvider);
    final householdsAsync = ref.watch(householdProvider);
    
    return Scaffold(
      appBar: AppBar(
        title: Row(
          children: [
            if (currentHousehold != null) ...[
              CircleAvatar(
                backgroundColor: Theme.of(context).colorScheme.primary,
                radius: 16,
                child: Text(
                  currentHousehold.name.isNotEmpty 
                      ? currentHousehold.name[0].toUpperCase() 
                      : 'H',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      currentHousehold.name,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    Text(
                      '${currentHousehold.members.length} members',
                      style: Theme.of(context).textTheme.bodySmall?.copyWith(
                        color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
                      ),
                    ),
                  ],
                ),
              ),
              IconButton(
                onPressed: () => _showHouseholdSwitcher(context),
                icon: const Icon(Icons.keyboard_arrow_down),
                tooltip: 'Switch Household',
              ),
            ] else ...[
              const Text('Sage'),
            ],
          ],
        ),
        actions: [
          if (user != null)
            PopupMenuButton<String>(
              onSelected: (value) {
                switch (value) {
                  case 'households':
                    HouseholdListRoute.goToList(context);
                    break;
                  case 'logout':
                    _showLogoutConfirmation(context);
                    break;
                }
              },
              itemBuilder: (context) => [
                const PopupMenuItem(
                  value: 'households',
                  child: Row(
                    children: [
                      Icon(Icons.home),
                      SizedBox(width: 8),
                      Text('Manage Households'),
                    ],
                  ),
                ),
                const PopupMenuItem(
                  value: 'logout',
                  child: Row(
                    children: [
                      Icon(Icons.logout),
                      SizedBox(width: 8),
                      Text('Log Out'),
                    ],
                  ),
                ),
              ],
            ),
        ],
      ),
      body: householdsAsync.when(
        loading: () => const Center(
          child: CircularProgressIndicator(),
        ),
        error: (error, stack) => Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                Icons.error,
                size: 64,
                color: Theme.of(context).colorScheme.error,
              ),
              const SizedBox(height: 16),
              Text(
                'Failed to load households',
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const SizedBox(height: 8),
              Text(
                error.toString(),
                style: Theme.of(context).textTheme.bodyMedium,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: () => Navigator.of(context).pushNamed('/households'),
                child: const Text('Manage Households'),
              ),
            ],
          ),
        ),
        data: (households) {
          if (households.isEmpty) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    Icons.home_outlined,
                    size: 96,
                    color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3),
                  ),
                  const SizedBox(height: 24),
                  Text(
                    'No Household Selected',
                    style: Theme.of(context).textTheme.headlineSmall,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    'Create or join a household to start managing your inventory',
                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                      color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
                    ),
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 32),
                  ElevatedButton.icon(
                    onPressed: () => HouseholdListRoute.goToList(context),
                    icon: const Icon(Icons.home),
                    label: const Text('Manage Households'),
                  ),
                ],
              ),
            );
          }
          
          if (currentHousehold == null) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          
          // Normal home page content with inventory
          return _buildInventoryContent(context, ref, currentHousehold);
        },
      ),
    );
  }
  
  Widget _buildInventoryContent(BuildContext context, WidgetRef ref, HouseholdEntity household) {
    // This would be the normal inventory/home content
    // For now, show placeholder
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.inventory_2_outlined,
            size: 96,
            color: Theme.of(context).colorScheme.primary,
          ),
          const SizedBox(height: 24),
          Text(
            'Welcome to ${household.name}!',
            style: Theme.of(context).textTheme.headlineSmall,
          ),
          const SizedBox(height: 8),
          Text(
            'Your inventory will appear here once you start adding items',
            style: Theme.of(context).textTheme.bodyMedium?.copyWith(
              color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
            ),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 32),
          const Text(
            'Phase 3 will bring barcode scanning and item management',
            style: TextStyle(fontStyle: FontStyle.italic),
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
  
  void _showHouseholdSwitcher(BuildContext context) {
    // This would show a bottom sheet or dialog to switch households
    // For now, navigate to household list
    HouseholdListRoute.goToList(context);
  }
  
  void _showLogoutConfirmation(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Log Out'),
        content: const Text('Are you sure you want to log out?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              Navigator.of(context).pop();
              ref.read(authProvider.notifier).signOut();
            },
            style: TextButton.styleFrom(
              foregroundColor: Theme.of(context).colorScheme.error,
            ),
            child: const Text('Log Out'),
          ),
        ],
      ),
    );
  }
}
```

Add household context display and switching functionality.
Include proper loading and error states.
Add navigation to household management.
flutter analyze lib/features/home/presentation/pages/home_page.dart passes Home page includes household context with navigation to household management Update Main Provider Registration lib/main.dart Register household providers in main.dart:
1. Add household provider imports:
```dart
import 'features/household/providers/household_provider.dart';
```

2. Add household providers to ProviderScope overrides:
```dart
ProviderScope(
  overrides: [
    // Auth providers (existing)
    authRepositoryProvider.overrideWithValue(AuthRepositoryImpl(client)),
    authProvider.overrideWithValue(AuthProvider(authRepository)),
    
    // Household providers
    householdRemoteDatasourceProvider.overrideWithValue(HouseholdRemoteDatasource(client)),
    householdRepositoryProvider.overrideWithValue(HouseholdRepositoryImpl(householdRemoteDatasource)),
  ],
  child: MyApp(),
)
```

Ensure all household providers are properly initialized with dependencies.
flutter analyze lib/main.dart passes Main app includes household provider registration with proper dependencies 1. Router includes household routes with proper authentication and household state guards 2. Auth provider integrates household loading with authentication state changes 3. Home page displays current household context with navigation to household management 4. Household state is properly initialized and synchronized across the app 5. Navigation flow handles all household states seamlessly

<success_criteria> Household functionality is fully integrated into app navigation and authentication flow with seamless user experience ready for production. </success_criteria>

After completion, create `.planning/phases/02-household-creation/02-06-SUMMARY.md` with integration completion summary