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
This commit is contained in:
679
.planning/phases/02-household-creation/02-05-PLAN.md
Normal file
679
.planning/phases/02-household-creation/02-05-PLAN.md
Normal file
@@ -0,0 +1,679 @@
|
||||
---
|
||||
phase: 02-household-creation
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: [02-04]
|
||||
files_modified:
|
||||
- lib/features/household/presentation/pages/join_household_page.dart
|
||||
- lib/features/household/presentation/pages/household_list_page.dart
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Users can join households using 8-character invite codes with validation"
|
||||
- "Household list page shows all user households with clear actions"
|
||||
- "Join flow provides immediate feedback on invalid/expired codes"
|
||||
- "Household management integrates with navigation and authentication state"
|
||||
- "All operations handle loading states and errors gracefully"
|
||||
artifacts:
|
||||
- path: "lib/features/household/presentation/pages/join_household_page.dart"
|
||||
provides: "Household joining interface with invite code validation"
|
||||
contains: "JoinHouseholdPage", "invite code input", "validation feedback"
|
||||
- path: "lib/features/household/presentation/pages/household_list_page.dart"
|
||||
provides: "Household overview and management hub"
|
||||
contains: "HouseholdListPage", "household cards", "create/join actions"
|
||||
key_links:
|
||||
- from: "lib/features/household/presentation/pages/join_household_page.dart"
|
||||
to: "lib/features/household/providers/household_provider.dart"
|
||||
via: "household joining use case"
|
||||
pattern: "ref.read.*householdProvider.*joinHousehold"
|
||||
- from: "lib/features/household/presentation/pages/household_list_page.dart"
|
||||
to: "lib/features/household/presentation/widgets/household_card.dart"
|
||||
via: "household display"
|
||||
pattern: "HouseholdCard.*onTap"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Complete household management UI with join household flow and household list hub with navigation integration.
|
||||
|
||||
Purpose: Provide complete household management experience where users can join existing households and manage all their households from one central location.
|
||||
Output: Full household management UI ready for navigation integration and user testing.
|
||||
</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
|
||||
|
||||
# UI Pattern References
|
||||
@lib/features/authentication/presentation/pages/signup_page.dart
|
||||
@lib/features/authentication/presentation/pages/login_page.dart
|
||||
@lib/features/authentication/presentation/widgets/auth_button.dart
|
||||
@lib/features/household/presentation/widgets/household_card.dart
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Create Join Household Page</name>
|
||||
<files>lib/features/household/presentation/pages/join_household_page.dart</files>
|
||||
<action>
|
||||
Create join household page with invite code validation:
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../providers/household_provider.dart';
|
||||
import '../../domain/exceptions/household_exceptions.dart';
|
||||
import '../../domain/entities/household_entity.dart';
|
||||
|
||||
class JoinHouseholdPage extends ConsumerStatefulWidget {
|
||||
const JoinHouseholdPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<JoinHouseholdPage> createState() => _JoinHouseholdPageState();
|
||||
}
|
||||
|
||||
class _JoinHouseholdPageState extends ConsumerState<JoinHouseholdPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _codeController = TextEditingController();
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
HouseholdEntity? _joinedHousehold;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_codeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _joinHousehold() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final user = ref.read(authProvider).user;
|
||||
if (user == null) throw Exception('User not authenticated');
|
||||
|
||||
final household = await ref.read(householdProvider.notifier)
|
||||
.joinHousehold(_codeController.text.trim(), user.id);
|
||||
|
||||
setState(() {
|
||||
_joinedHousehold = household;
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
// Auto-close after successful join
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(household);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = _getErrorMessage(e);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _getErrorMessage(dynamic error) {
|
||||
if (error is HouseholdValidationException) {
|
||||
return error.message;
|
||||
} else if (error is HouseholdOperationException) {
|
||||
return error.message;
|
||||
} else if (error is InviteCodeException) {
|
||||
return error.message;
|
||||
}
|
||||
return 'Failed to join household. Please check the invite code and try again.';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Join Household'),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
elevation: 0,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 32),
|
||||
Icon(
|
||||
Icons.group_add,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Join a Household',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Enter the 8-character invite code from your household member',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
TextFormField(
|
||||
controller: _codeController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Invite Code',
|
||||
hintText: 'ABCD1234',
|
||||
prefixIcon: const Icon(Icons.key),
|
||||
helperText: '8 characters, case-insensitive',
|
||||
),
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
textInputAction: TextInputAction.done,
|
||||
inputFormatters: [
|
||||
UpperCaseTextFormatter(),
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[A-Z0-9]')),
|
||||
LengthLimitingTextInputFormatter(8),
|
||||
],
|
||||
onChanged: (value) {
|
||||
// Auto-format and move to next field after 8 chars
|
||||
if (value.length == 8 && !_isLoading) {
|
||||
_formKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Please enter an invite code';
|
||||
}
|
||||
if (value.trim().length != 8) {
|
||||
return 'Invite code must be 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onFieldSubmitted: (_) => _joinHousehold(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_error != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_error!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_joinedHousehold != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Successfully joined!',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'You are now a member of ${_joinedHousehold!.name}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_joinedHousehold == null) ...[
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _joinHousehold,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Join Household'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper formatter for uppercase input
|
||||
class UpperCaseTextFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
return TextEditingValue(
|
||||
text: newValue.text.toUpperCase(),
|
||||
selection: newValue.selection,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Include auto-formatting, validation, and clear success/error feedback.
|
||||
Follow authentication page patterns for consistency.
|
||||
</action>
|
||||
<verify>flutter analyze lib/features/household/presentation/pages/join_household_page.dart passes</verify>
|
||||
<done>Join household page provides invite code validation with immediate feedback</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Create Household List Page</name>
|
||||
<files>lib/features/household/presentation/pages/household_list_page.dart</files>
|
||||
<action>
|
||||
Create household list page as management hub:
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../widgets/household_card.dart';
|
||||
import '../widgets/invite_code_dialog.dart';
|
||||
import '../../providers/household_provider.dart';
|
||||
import '../../domain/entities/household_entity.dart';
|
||||
import '../pages/create_household_page.dart';
|
||||
import '../pages/join_household_page.dart';
|
||||
|
||||
class HouseholdListPage extends ConsumerStatefulWidget {
|
||||
const HouseholdListPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<HouseholdListPage> createState() => _HouseholdListPageState();
|
||||
}
|
||||
|
||||
class _HouseholdListPageState extends ConsumerState<HouseholdListPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadHouseholds();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadHouseholds() async {
|
||||
final user = ref.read(authProvider).user;
|
||||
if (user != null) {
|
||||
await ref.read(householdProvider.notifier).loadUserHouseholds(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showCreateHousehold() async {
|
||||
final result = await Navigator.of(context).push<HouseholdEntity>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const CreateHouseholdPage(),
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
// Household was created, show success message
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Welcome to ${result.name}!'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showJoinHousehold() async {
|
||||
final result = await Navigator.of(context).push<HouseholdEntity>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const JoinHouseholdPage(),
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
// Household was joined, show success message
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('You joined ${result.name}!'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showInviteDialog(HouseholdEntity household) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => InviteCodeDialog(
|
||||
householdId: household.id,
|
||||
householdName: household.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleHouseholdTap(HouseholdEntity household) async {
|
||||
// Set as current household and navigate to home/inventory
|
||||
ref.read(currentHouseholdProvider.notifier).state = household;
|
||||
|
||||
if (mounted) {
|
||||
// Navigate to inventory/home page
|
||||
Navigator.of(context).pushReplacementNamed('/home');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(authProvider).user;
|
||||
final householdsAsync = ref.watch(householdProvider);
|
||||
final currentHousehold = ref.watch(currentHouseholdProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('My Households'),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
elevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _loadHouseholds,
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Refresh',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _loadHouseholds,
|
||||
child: 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: _loadHouseholds,
|
||||
child: const Text('Try Again'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
data: (households) {
|
||||
if (households.isEmpty) {
|
||||
return _buildEmptyState(context);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (currentHousehold != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.home,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Current Household',
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
currentHousehold.name,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _handleHouseholdTap(currentHousehold),
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
itemCount: households.length,
|
||||
itemBuilder: (context, index) {
|
||||
final household = households[index];
|
||||
return HouseholdCard(
|
||||
household: household,
|
||||
onTap: () => _handleHouseholdTap(household),
|
||||
onGenerateInvite: household.canInviteMembers()
|
||||
? () => _showInviteDialog(household)
|
||||
: null,
|
||||
onLeave: household.members.length > 1
|
||||
? () => _showLeaveConfirmation(household)
|
||||
: null,
|
||||
showActions: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FloatingActionButton.extended(
|
||||
onPressed: _showCreateHousehold,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Create'),
|
||||
heroTag: 'create',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FloatingActionButton.extended(
|
||||
onPressed: _showJoinHousehold,
|
||||
icon: const Icon(Icons.group_add),
|
||||
label: const Text('Join'),
|
||||
heroTag: 'join',
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
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 Households Yet',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Create your first household or join an existing one to start sharing 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: _showCreateHousehold,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Create Household'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _showJoinHousehold,
|
||||
icon: const Icon(Icons.group_add),
|
||||
label: const Text('Join Household'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showLeaveConfirmation(HouseholdEntity household) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Leave Household'),
|
||||
content: Text('Are you sure you want to leave ${household.name}?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Leave'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
// Handle leaving household
|
||||
// This would be implemented in the repository/provider
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Include empty states, loading indicators, error handling, and refresh functionality.
|
||||
Provide clear navigation to household creation, joining, and inventory management.
|
||||
</action>
|
||||
<verify>flutter analyze lib/features/household/presentation/pages/household_list_page.dart passes</verify>
|
||||
<done>Household list page provides complete management hub with navigation integration</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. Join household page provides invite code validation with auto-formatting and immediate feedback
|
||||
2. Household list page shows all user households with clear management actions
|
||||
3. Navigation between create, join, and list flows works seamlessly
|
||||
4. Empty states and error handling provide clear user guidance
|
||||
5. All components follow existing authentication UI patterns for consistency
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Household management UI is complete with join flow, list hub, and navigation integration ready for user testing.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-household-creation/02-05-SUMMARY.md` with household UI completion summary
|
||||
</output>
|
||||
Reference in New Issue
Block a user