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
367 lines
13 KiB
Markdown
367 lines
13 KiB
Markdown
---
|
|
phase: 02-household-creation
|
|
plan: 03
|
|
type: execute
|
|
wave: 2
|
|
depends_on: [02-01]
|
|
files_modified:
|
|
- lib/features/household/providers/household_provider.dart
|
|
- lib/features/household/domain/usecases/create_household_usecase.dart
|
|
- lib/features/household/domain/usecases/generate_invite_code_usecase.dart
|
|
- lib/features/household/domain/usecases/join_household_usecase.dart
|
|
- lib/features/household/domain/usecases/get_user_households_usecase.dart
|
|
autonomous: true
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Household state management provides reactive updates across the app"
|
|
- "Use cases encapsulate business logic for household operations"
|
|
- "Provider integrates with existing authentication state"
|
|
- "Household operations handle loading states and errors properly"
|
|
artifacts:
|
|
- path: "lib/features/household/providers/household_provider.dart"
|
|
provides: "Global household state management"
|
|
contains: "class HouseholdProvider", "Riverpod", "AsyncValue"
|
|
- path: "lib/features/household/domain/usecases/create_household_usecase.dart"
|
|
provides: "Household creation business logic"
|
|
contains: "class CreateHouseholdUseCase", "Future<HouseholdEntity>"
|
|
- path: "lib/features/household/domain/usecases/join_household_usecase.dart"
|
|
provides: "Household joining logic with validation"
|
|
contains: "class JoinHouseholdUseCase", "invite code validation"
|
|
- path: "lib/features/household/domain/usecases/get_user_households_usecase.dart"
|
|
provides: "User household retrieval logic"
|
|
contains: "class GetUserHouseholdsUseCase", "List<HouseholdEntity>"
|
|
key_links:
|
|
- from: "lib/features/household/providers/household_provider.dart"
|
|
to: "lib/features/household/domain/usecases/create_household_usecase.dart"
|
|
via: "dependency injection"
|
|
pattern: "CreateHouseholdUseCase.*createHouseholdUseCase"
|
|
- from: "lib/features/household/providers/household_provider.dart"
|
|
to: "lib/providers/auth_provider.dart"
|
|
via: "authentication state"
|
|
pattern: "ref.watch.*authProvider"
|
|
---
|
|
|
|
<objective>
|
|
Implement household state management and business logic use cases following clean architecture patterns.
|
|
|
|
Purpose: Provide reactive household state that integrates with authentication and handles all household operations with proper error handling.
|
|
Output: Complete state management layer with use cases and Riverpod provider ready for UI integration.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@~/.opencode/get-shit-done/workflows/execute-plan.md
|
|
@~/.opencode/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
|
|
# State Management References
|
|
@lib/providers/auth_provider.dart
|
|
@lib/features/authentication/data/repositories/auth_repository_impl.dart
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Create Household Use Cases</name>
|
|
<files>
|
|
lib/features/household/domain/usecases/create_household_usecase.dart
|
|
lib/features/household/domain/usecases/generate_invite_code_usecase.dart
|
|
lib/features/household/domain/usecases/join_household_usecase.dart
|
|
lib/features/household/domain/usecases/get_user_households_usecase.dart
|
|
</files>
|
|
<action>
|
|
Create use cases for household operations following clean architecture:
|
|
|
|
1. CreateHouseholdUseCase (create_household_usecase.dart):
|
|
```dart
|
|
class CreateHouseholdUseCase {
|
|
final HouseholdRepository repository;
|
|
|
|
CreateHouseholdUseCase(this.repository);
|
|
|
|
Future<HouseholdEntity> call(String name, String userId) async {
|
|
if (name.trim().isEmpty) {
|
|
throw HouseholdValidationException('Household name cannot be empty');
|
|
}
|
|
|
|
if (name.length > 100) {
|
|
throw HouseholdValidationException('Household name too long (max 100 characters)');
|
|
}
|
|
|
|
return await repository.createHousehold(name.trim(), userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
2. GenerateInviteCodeUseCase (generate_invite_code_usecase.dart):
|
|
```dart
|
|
class GenerateInviteCodeUseCase {
|
|
final HouseholdRepository repository;
|
|
|
|
GenerateInviteCodeUseCase(this.repository);
|
|
|
|
Future<InviteCodeModel> call(String householdId, String userId) async {
|
|
// Check if user is owner (will be validated in repository)
|
|
return await repository.generateInviteCode(householdId, userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
3. JoinHouseholdUseCase (join_household_usecase.dart):
|
|
```dart
|
|
class JoinHouseholdUseCase {
|
|
final HouseholdRepository repository;
|
|
|
|
JoinHouseholdUseCase(this.repository);
|
|
|
|
Future<HouseholdEntity> call(String inviteCode, String userId) async {
|
|
if (inviteCode.trim().isEmpty) {
|
|
throw HouseholdValidationException('Invite code cannot be empty');
|
|
}
|
|
|
|
if (inviteCode.length != 8) {
|
|
throw HouseholdValidationException('Invite code must be 8 characters');
|
|
}
|
|
|
|
return await repository.joinHousehold(inviteCode.trim().toUpperCase(), userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
4. GetUserHouseholdsUseCase (get_user_households_usecase.dart):
|
|
```dart
|
|
class GetUserHouseholdsUseCase {
|
|
final HouseholdRepository repository;
|
|
|
|
GetUserHouseholdsUseCase(this.repository);
|
|
|
|
Future<List<HouseholdEntity>> call(String userId) async {
|
|
return await repository.getUserHouseholds(userId);
|
|
}
|
|
}
|
|
```
|
|
|
|
Include proper validation and business logic in each use case.
|
|
Create custom exception classes for household operations.
|
|
</action>
|
|
<verify>flutter analyze lib/features/household/domain/usecases/*.dart passes</verify>
|
|
<done>Household use cases encapsulate business logic with proper validation</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Create Household Provider</name>
|
|
<files>lib/features/household/providers/household_provider.dart</files>
|
|
<action>
|
|
Create Riverpod provider for household state management:
|
|
```dart
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
// Use case providers
|
|
final createHouseholdUseCaseProvider = Provider<CreateHouseholdUseCase>((ref) {
|
|
final repository = ref.watch(householdRepositoryProvider);
|
|
return CreateHouseholdUseCase(repository);
|
|
});
|
|
|
|
final generateInviteCodeUseCaseProvider = Provider<GenerateInviteCodeUseCase>((ref) {
|
|
final repository = ref.watch(householdRepositoryProvider);
|
|
return GenerateInviteCodeUseCase(repository);
|
|
});
|
|
|
|
final joinHouseholdUseCaseProvider = Provider<JoinHouseholdUseCase>((ref) {
|
|
final repository = ref.watch(householdRepositoryProvider);
|
|
return JoinHouseholdUseCase(repository);
|
|
});
|
|
|
|
final getUserHouseholdsUseCaseProvider = Provider<GetUserHouseholdsUseCase>((ref) {
|
|
final repository = ref.watch(householdRepositoryProvider);
|
|
return GetUserHouseholdsUseCase(repository);
|
|
});
|
|
|
|
// Main household provider
|
|
class HouseholdProvider extends StateNotifier<AsyncValue<List<HouseholdEntity>>> {
|
|
final GetUserHouseholdsUseCase _getUserHouseholdsUseCase;
|
|
final CreateHouseholdUseCase _createHouseholdUseCase;
|
|
final JoinHouseholdUseCase _joinHouseholdUseCase;
|
|
final GenerateInviteCodeUseCase _generateInviteCodeUseCase;
|
|
|
|
HouseholdProvider(
|
|
this._getUserHouseholdsUseCase,
|
|
this._createHouseholdUseCase,
|
|
this._joinHouseholdUseCase,
|
|
this._generateInviteCodeUseCase,
|
|
) : super(const AsyncValue.loading()) {
|
|
_initialize();
|
|
}
|
|
|
|
Future<void> _initialize() async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
// Will be called when auth state is available
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
Future<void> loadUserHouseholds(String userId) async {
|
|
state = const AsyncValue.loading();
|
|
try {
|
|
final households = await _getUserHouseholdsUseCase(userId);
|
|
state = AsyncValue.data(households);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
Future<HouseholdEntity> createHousehold(String name, String userId) async {
|
|
try {
|
|
final household = await _createHouseholdUseCase(name, userId);
|
|
|
|
// Refresh the list
|
|
await loadUserHouseholds(userId);
|
|
|
|
return household;
|
|
} catch (e) {
|
|
throw HouseholdOperationException('Failed to create household: $e');
|
|
}
|
|
}
|
|
|
|
Future<HouseholdEntity> joinHousehold(String inviteCode, String userId) async {
|
|
try {
|
|
final household = await _joinHouseholdUseCase(inviteCode, userId);
|
|
|
|
// Refresh the list
|
|
await loadUserHouseholds(userId);
|
|
|
|
return household;
|
|
} catch (e) {
|
|
throw HouseholdOperationException('Failed to join household: $e');
|
|
}
|
|
}
|
|
|
|
Future<InviteCodeModel> generateInviteCode(String householdId, String userId) async {
|
|
try {
|
|
return await _generateInviteCodeUseCase(householdId, userId);
|
|
} catch (e) {
|
|
throw HouseholdOperationException('Failed to generate invite code: $e');
|
|
}
|
|
}
|
|
|
|
void clearHouseholds() {
|
|
state = const AsyncValue.data([]);
|
|
}
|
|
}
|
|
|
|
// Provider instance
|
|
final householdProvider = StateNotifierProvider<HouseholdProvider, AsyncValue<List<HouseholdEntity>>>((ref) {
|
|
return HouseholdProvider(
|
|
ref.read(getUserHouseholdsUseCaseProvider),
|
|
ref.read(createHouseholdUseCaseProvider),
|
|
ref.read(joinHouseholdUseCaseProvider),
|
|
ref.read(generateInviteCodeUseCaseProvider),
|
|
);
|
|
});
|
|
|
|
// Current selected household provider
|
|
final currentHouseholdProvider = StateProvider<HouseholdEntity?>((ref) => null);
|
|
|
|
// Repository provider (to be added to main providers file)
|
|
final householdRepositoryProvider = Provider<HouseholdRepository>((ref) {
|
|
final datasource = ref.read(householdRemoteDatasourceProvider);
|
|
return HouseholdRepositoryImpl(datasource);
|
|
});
|
|
|
|
final householdRemoteDatasourceProvider = Provider<HouseholdRemoteDatasource>((ref) {
|
|
final client = ref.read(supabaseClientProvider);
|
|
return HouseholdRemoteDatasource(client);
|
|
});
|
|
```
|
|
|
|
Follow auth provider patterns for consistency.
|
|
Include proper error handling and state transitions.
|
|
</action>
|
|
<verify>flutter analyze lib/features/household/providers/household_provider.dart passes</verify>
|
|
<done>Household provider provides reactive state management with proper integration</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Create Household Exception Classes</name>
|
|
<files>lib/features/household/domain/exceptions/household_exceptions.dart</files>
|
|
<action>
|
|
Create custom exception classes for household operations:
|
|
```dart
|
|
// Base household exception
|
|
abstract class HouseholdException implements Exception {
|
|
final String message;
|
|
const HouseholdException(this.message);
|
|
|
|
@override
|
|
String toString() => 'HouseholdException: $message';
|
|
}
|
|
|
|
// Validation exceptions
|
|
class HouseholdValidationException extends HouseholdException {
|
|
const HouseholdValidationException(String message) : super(message);
|
|
|
|
@override
|
|
String toString() => 'HouseholdValidationException: $message';
|
|
}
|
|
|
|
// Operation exceptions
|
|
class HouseholdOperationException extends HouseholdException {
|
|
const HouseholdOperationException(String message) : super(message);
|
|
|
|
@override
|
|
String toString() => 'HouseholdOperationException: $message';
|
|
}
|
|
|
|
// Specific household exceptions
|
|
class HouseholdNotFoundException extends HouseholdException {
|
|
const HouseholdNotFoundException(String message) : super(message);
|
|
|
|
@override
|
|
String toString() => 'HouseholdNotFoundException: $message';
|
|
}
|
|
|
|
class InviteCodeException extends HouseholdException {
|
|
const InviteCodeException(String message) : super(message);
|
|
|
|
@override
|
|
String toString() => 'InviteCodeException: $message';
|
|
}
|
|
|
|
class HouseholdMembershipException extends HouseholdException {
|
|
const HouseholdMembershipException(String message) : super(message);
|
|
|
|
@override
|
|
String toString() => 'HouseholdMembershipException: $message';
|
|
}
|
|
```
|
|
|
|
Follow authentication exception patterns for consistency.
|
|
</action>
|
|
<verify>flutter analyze lib/features/household/domain/exceptions/household_exceptions.dart passes</verify>
|
|
<done>Household exception classes provide clear error categorization</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
1. Use cases encapsulate business logic with proper validation
|
|
2. Household provider provides reactive state management following Riverpod patterns
|
|
3. Exception classes provide clear error categorization
|
|
4. Integration with authentication state is properly structured
|
|
5. All household operations handle loading states and errors appropriately
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Household state management is complete with use cases, provider, and exception handling ready for UI integration.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/02-household-creation/02-03-SUMMARY.md` with state management implementation summary
|
|
</output> |