import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../core/responsive/responsive_layout.dart'; import '../../../core/theme/app_typography.dart'; import '../../../core/theme/theme_extensions.dart'; import '../../../services/auth/auth_providers.dart'; import '../../../services/clubs/club_models.dart'; import '../../../services/clubs/club_providers.dart'; import '../../../services/clubs/club_service.dart'; import 'club_membership_controller.dart'; import 'club_membership_presentation.dart'; import 'widgets/club_monogram.dart'; import 'widgets/club_roster_tab.dart'; import 'widgets/club_wall_tab.dart'; import 'widgets/officer_badge.dart'; /// Navigation tabs on the ClubDetailScreen. enum ClubDetailTab { overview, wall, roster } /// Authoritative detail and membership lifecycle screen for a MotorSignal Club. /// Enables riders to discover, join, request admission, withdraw requests, /// accept/decline invitations, explore the Club Wall, and review the full Member Roster. class ClubDetailScreen extends ConsumerStatefulWidget { final String clubId; const ClubDetailScreen({ super.key, required this.clubId, }); @override ConsumerState createState() => _ClubDetailScreenState(); } class _ClubDetailScreenState extends ConsumerState { ClubDetailTab _selectedTab = ClubDetailTab.overview; @override Widget build(BuildContext context) { final colors = context.motorColors; final theme = Theme.of(context); final user = ref.watch(currentUserProvider); final isAuthenticated = user != null && !user.isAnonymous; final currentUserId = user?.uid; final clubAsync = ref.watch(clubDetailProvider(widget.clubId)); final memberAsync = ref.watch(currentMemberProvider(widget.clubId)); final actionState = ref.watch(clubMembershipControllerProvider(widget.clubId)); final isMutating = actionState.isSubmitting; // Listen for feedback snackbars ref.listen(clubMembershipControllerProvider(widget.clubId), (prev, next) { if (next.error != null && next.error != prev?.error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(next.error!), backgroundColor: colors.raceRed, behavior: SnackBarBehavior.floating, ), ); } if (next.success != null && next.success != prev?.success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(next.success!), backgroundColor: colors.launchGreen, behavior: SnackBarBehavior.floating, ), ); } }); return Scaffold( appBar: AppBar( title: const Text( 'Club', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w700, ), ), ), body: SafeArea( child: clubAsync.when( data: (club) { if (club == null) { return Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.shield_outlined, size: 48, color: colors.warningAmber), const SizedBox(height: 16), const Text( 'Club Not Found', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 18, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), Text( 'This club may have been archived or does not exist.', textAlign: TextAlign.center, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => context.go('/social/clubs'), icon: const Icon(Icons.arrow_back, size: 16), label: const Text('Back to Clubs'), style: ElevatedButton.styleFrom( backgroundColor: colors.surfaceContainerHigh, foregroundColor: colors.cyberCyan, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: colors.outlineVariant), ), ), ), ], ), ), ); } if (club.isArchived) { return Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.archive_outlined, size: 48, color: colors.warningAmber), const SizedBox(height: 16), const Text( 'Club Archived', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 18, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), Text( 'This club has been archived by leadership and is no longer active.', textAlign: TextAlign.center, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => context.go('/social/clubs'), icon: const Icon(Icons.arrow_back, size: 16), label: const Text('Back to Clubs'), style: ElevatedButton.styleFrom( backgroundColor: colors.surfaceContainerHigh, foregroundColor: colors.cyberCyan, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: colors.outlineVariant), ), ), ), ], ), ), ); } final myMembership = memberAsync.valueOrNull; return ResponsiveLayout( builder: (context, responsive) { return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 750), child: SingleChildScrollView( padding: const EdgeInsets.all(18.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Club Hero Identity Card _buildHeroCard(context, club, myMembership, colors, theme), const SizedBox(height: 14), // Segmented Navigation Tab Bar _buildTabBar(colors, theme), const SizedBox(height: 16), if (_selectedTab == ClubDetailTab.overview) ...[ // Interactive Membership Lifecycle Action Card _buildMembershipActionCard( context, ref, club, memberAsync, currentUserId, isAuthenticated, isMutating, colors, theme, ), const SizedBox(height: 16), // Quick Navigation Teasers (Club Wall & Roster) _buildQuickShortcutsCard(context, club, colors, theme), const SizedBox(height: 18), // Club Rides Card _buildClubRidesCard( context, ref, club, myMembership, colors, theme, ), const SizedBox(height: 18), // Description Card _buildDescriptionCard(context, club, colors, theme), const SizedBox(height: 18), // Metadata Card (Region, Focus, Dates) _buildMetadataCard(context, club, colors, theme), ] else if (_selectedTab == ClubDetailTab.wall) ...[ ClubWallTab(club: club, member: myMembership), ] else if (_selectedTab == ClubDetailTab.roster) ...[ ClubRosterTab(club: club, myMembership: myMembership), ], ], ), ), ), ); }, ); }, loading: () => Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(colors.cyberCyan), ), ), error: (err, _) => Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.shield_outlined, size: 36, color: colors.warningAmber), const SizedBox(height: 12), const Text( 'Club Unavailable', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 18, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), Text( 'Unable to display this club at this time.', textAlign: TextAlign.center, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => context.go('/social/clubs'), icon: const Icon(Icons.arrow_back, size: 16), label: const Text('Back to Clubs'), style: ElevatedButton.styleFrom( backgroundColor: colors.surfaceContainerHigh, foregroundColor: colors.cyberCyan, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: colors.outlineVariant), ), ), ), ], ), ), ), ), ), ); } Widget _buildHeroCard( BuildContext context, Club club, ClubMember? member, MotorSignalThemeColors colors, ThemeData theme, ) { return Container( padding: const EdgeInsets.all(20.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(16), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Column( children: [ ClubMonogram(name: club.name, size: 68, fontSize: 26), const SizedBox(height: 14), Text( club.name, textAlign: TextAlign.center, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 20, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 4), Text( '@${club.slug}', style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 14, fontWeight: FontWeight.w600, color: colors.cyberCyan, ), ), const SizedBox(height: 14), Wrap( spacing: 8, runSpacing: 6, alignment: WrapAlignment.center, children: [ _buildPill( icon: Icons.people_outline, label: '${club.memberCount} ${club.memberCount == 1 ? 'member' : 'members'}', colors: colors, theme: theme, ), _buildPill( icon: club.admissionPolicy == ClubAdmissionPolicy.open ? Icons.group_add_outlined : (club.admissionPolicy == ClubAdmissionPolicy.request ? Icons.how_to_reg_outlined : Icons.mail_outline), label: club.admissionPolicy.label, colors: colors, theme: theme, tintColor: colors.cyberCyan, ), _buildPill( icon: club.visibility == ClubVisibility.public ? Icons.public : Icons.visibility_off_outlined, label: club.visibility.label, colors: colors, theme: theme, ), if (member != null && member.isActive && (member.role == ClubRole.owner || member.role == ClubRole.admin)) _buildPill( icon: Icons.verified_user_outlined, label: 'ROLE: ${member.role.label.toUpperCase()}', colors: colors, theme: theme, tintColor: colors.launchGreen, ), if (member != null && member.isActive && member.displayRank != null) OfficerBadge( rank: member.displayRank, role: member.role, fontSize: 11, ), ], ), ], ), ); } Widget _buildTabBar(MotorSignalThemeColors colors, ThemeData theme) { return Container( decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all(color: colors.outlineVariant, width: 0.8), ), padding: const EdgeInsets.all(4), child: Row( children: [ _buildTabButton( tab: ClubDetailTab.overview, label: 'OVERVIEW', icon: Icons.info_outline, colors: colors, ), _buildTabButton( tab: ClubDetailTab.wall, label: 'CLUB WALL', icon: Icons.forum_outlined, colors: colors, ), _buildTabButton( tab: ClubDetailTab.roster, label: 'ROSTER', icon: Icons.groups_outlined, colors: colors, ), ], ), ); } Widget _buildTabButton({ required ClubDetailTab tab, required String label, required IconData icon, required MotorSignalThemeColors colors, }) { final isSelected = _selectedTab == tab; return Expanded( child: InkWell( onTap: () => setState(() => _selectedTab = tab), borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(vertical: 9), decoration: BoxDecoration( color: isSelected ? colors.cyberCyan.withValues(alpha: 0.15) : Colors.transparent, borderRadius: BorderRadius.circular(8), border: isSelected ? Border.all(color: colors.cyberCyan.withValues(alpha: 0.6), width: 0.8) : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, size: 14, color: isSelected ? colors.cyberCyan : Colors.white60, ), const SizedBox(width: 6), Text( label, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, letterSpacing: 0.3, color: isSelected ? colors.cyberCyan : Colors.white60, ), ), ], ), ), ), ); } Widget _buildQuickShortcutsCard( BuildContext context, Club club, MotorSignalThemeColors colors, ThemeData theme, ) { return Row( children: [ Expanded( child: InkWell( onTap: () => setState(() => _selectedTab = ClubDetailTab.wall), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: colors.cyberCyan.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.forum_outlined, size: 16, color: colors.cyberCyan), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Club Wall', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 12.5, fontWeight: FontWeight.w700, ), ), Text( 'Announcements & chat', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 10.5, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), Icon(Icons.chevron_right, size: 16, color: colors.cyberCyan), ], ), ), ), ), const SizedBox(width: 10), Expanded( child: InkWell( onTap: () => setState(() => _selectedTab = ClubDetailTab.roster), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: const Color(0xFFFFB800).withValues(alpha: 0.12), borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.groups_outlined, size: 16, color: Color(0xFFFFB800)), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Roster', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 12.5, fontWeight: FontWeight.w700, ), ), Text( '${club.memberCount} squad riders', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 10.5, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), Icon(Icons.chevron_right, size: 16, color: colors.cyberCyan), ], ), ), ), ), ], ); } Widget _buildMembershipActionCard( BuildContext context, WidgetRef ref, Club club, AsyncValue memberAsync, String? currentUserId, bool isAuthenticated, bool isMutating, MotorSignalThemeColors colors, ThemeData theme, ) { if (isAuthenticated && memberAsync.isLoading) { return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Row( children: [ SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(colors.cyberCyan), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Checking Membership Status...', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 2), Text( 'Verifying status with club registry.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), ], ), ); } if (isAuthenticated && memberAsync.hasError) { return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all(color: colors.warningAmber.withValues(alpha: 0.6), width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.warning_amber_rounded, size: 18, color: colors.warningAmber), const SizedBox(width: 8), const Text( 'Membership Status Unavailable', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 6), Text( 'Could not load your membership record. Check connection and retry.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: () => ref.invalidate(currentMemberProvider(club.id)), icon: const Icon(Icons.refresh, size: 16), label: const Text('Retry'), style: OutlinedButton.styleFrom( foregroundColor: colors.cyberCyan, side: BorderSide(color: colors.cyberCyan.withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), ), ), ], ), ); } final myMembership = memberAsync.valueOrNull; final presentation = ClubMembershipPresentation.resolve( club: club, member: myMembership, currentUserId: currentUserId, isAuthenticated: isAuthenticated, ); Color badgeBgColor; Color badgeTextColor; switch (presentation.badgeType) { case PresentationBadgeType.green: badgeBgColor = colors.launchGreen.withValues(alpha: 0.15); badgeTextColor = colors.launchGreen; break; case PresentationBadgeType.amber: badgeBgColor = colors.warningAmber.withValues(alpha: 0.15); badgeTextColor = colors.warningAmber; break; case PresentationBadgeType.cyan: badgeBgColor = colors.cyberCyan.withValues(alpha: 0.15); badgeTextColor = colors.cyberCyan; break; case PresentationBadgeType.neutral: badgeBgColor = colors.surfaceContainerHighest.withValues(alpha: 0.5); badgeTextColor = theme.colorScheme.onSurfaceVariant; break; } return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all( color: presentation.badgeType == PresentationBadgeType.amber ? colors.warningAmber.withValues(alpha: 0.6) : colors.outlineVariant, width: 0.8, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4), decoration: BoxDecoration( color: badgeBgColor, borderRadius: BorderRadius.circular(6), border: Border.all(color: badgeTextColor.withValues(alpha: 0.6), width: 0.8), ), child: Text( presentation.statusBadge, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11, fontWeight: FontWeight.w700, color: badgeTextColor, letterSpacing: 0.4, ), ), ), const SizedBox(width: 10), Expanded( child: Text( presentation.statusHeadline, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w700, ), ), ), ], ), const SizedBox(height: 10), Text( presentation.statusDescription, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, color: theme.colorScheme.onSurfaceVariant, height: 1.35, ), ), const SizedBox(height: 14), if (isMutating) Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: SizedBox( height: 24, width: 24, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(colors.cyberCyan), ), ), ), ) else ...[ if (presentation.state == ClubMembershipPresentationState.invited) Row( children: [ Expanded( child: OutlinedButton( onPressed: () { ref .read(clubMembershipControllerProvider(club.id).notifier) .declineInvitation(club.id); }, style: OutlinedButton.styleFrom( foregroundColor: colors.raceRed, side: BorderSide(color: colors.raceRed.withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text( 'Decline', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w600, ), ), ), ), const SizedBox(width: 12), Expanded( child: FilledButton.icon( onPressed: () { ref .read(clubMembershipControllerProvider(club.id).notifier) .acceptInvitation(club.id); }, style: FilledButton.styleFrom( backgroundColor: colors.launchGreen, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), icon: const Icon(Icons.check, size: 18), label: const Text( 'Accept', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w700, ), ), ), ), ], ) else if (presentation.primaryAction != null) SizedBox( width: double.infinity, child: _buildActionButton( context, ref, club, presentation, colors, ), ), ], ], ), ); } Widget _buildActionButton( BuildContext context, WidgetRef ref, Club club, ClubMembershipPresentation presentation, MotorSignalThemeColors colors, ) { final action = presentation.primaryAction!; final label = presentation.primaryActionLabel ?? ''; final icon = presentation.primaryActionIcon; if (action == ClubMembershipAction.signIn) { return FilledButton.icon( onPressed: () => context.push('/account'), style: FilledButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), icon: Icon(icon, size: 18), label: Text( label, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w700, ), ), ); } if (action == ClubMembershipAction.leaveClub) { return OutlinedButton.icon( onPressed: () => _confirmLeaveClub(context, ref, club, colors), style: OutlinedButton.styleFrom( foregroundColor: colors.raceRed, side: BorderSide(color: colors.raceRed.withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), icon: Icon(icon, size: 18), label: Text( label, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w600, ), ), ); } if (action == ClubMembershipAction.withdrawRequest) { return OutlinedButton.icon( onPressed: () { ref.read(clubMembershipControllerProvider(club.id).notifier).withdrawJoinRequest(club.id); }, style: OutlinedButton.styleFrom( foregroundColor: colors.warningAmber, side: BorderSide(color: colors.warningAmber.withValues(alpha: 0.6)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), icon: Icon(icon, size: 18), label: Text( label, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w600, ), ), ); } // Default: FilledButton (Join Club or Request to Join) return FilledButton.icon( onPressed: () { if (action == ClubMembershipAction.joinOpen) { ref.read(clubMembershipControllerProvider(club.id).notifier).joinOpenClub(club.id); } else if (action == ClubMembershipAction.requestToJoin) { ref.read(clubMembershipControllerProvider(club.id).notifier).requestToJoinClub(club.id); } }, style: FilledButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), icon: Icon(icon, size: 18), label: Text( label, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w700, ), ), ); } void _confirmLeaveClub( BuildContext context, WidgetRef ref, Club club, MotorSignalThemeColors colors, ) { showDialog( context: context, builder: (dialogCtx) => AlertDialog( scrollable: true, title: Text('Leave ${club.name}?'), content: const Text( 'Are you sure you want to leave this club? You will lose access to member-only rides and updates.', ), actions: [ TextButton( onPressed: () => Navigator.of(dialogCtx).pop(), child: const Text('Cancel'), ), FilledButton( style: FilledButton.styleFrom( backgroundColor: colors.raceRed, foregroundColor: Colors.white, ), onPressed: () { Navigator.of(dialogCtx).pop(); ref.read(clubMembershipControllerProvider(club.id).notifier).leaveClub(club.id); }, child: const Text('Leave Club'), ), ], ), ); } Widget _buildDescriptionCard( BuildContext context, Club club, MotorSignalThemeColors colors, ThemeData theme, ) { return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'About the Club', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 0.2, color: colors.cyberCyan, ), ), const SizedBox(height: 10), Text( club.description.isNotEmpty ? club.description : 'No club description provided by leadership.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13.5, color: club.description.isNotEmpty ? theme.colorScheme.onSurface : theme.colorScheme.onSurfaceVariant, height: 1.4, ), ), ], ), ); } Widget _buildMetadataCard( BuildContext context, Club club, MotorSignalThemeColors colors, ThemeData theme, ) { return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(14), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Club Details', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, letterSpacing: 0.2, color: colors.cyberCyan, ), ), const SizedBox(height: 12), _buildDetailRow( icon: Icons.location_on_outlined, title: 'Home Region', value: club.homeRegion?.trim().isNotEmpty == true ? club.homeRegion! : 'Global / Not Specified', colors: colors, theme: theme, ), const Divider(height: 18), _buildDetailRow( icon: Icons.two_wheeler_outlined, title: 'Motorcycle Focus', value: club.motorcycleFocus?.trim().isNotEmpty == true ? club.motorcycleFocus! : 'All Motorcycles & Disciplines', colors: colors, theme: theme, ), const Divider(height: 18), _buildDetailRow( icon: Icons.calendar_today_outlined, title: 'Established', value: '${club.createdAt.year}-${club.createdAt.month.toString().padLeft(2, '0')}-${club.createdAt.day.toString().padLeft(2, '0')}', colors: colors, theme: theme, ), ], ), ); } Widget _buildDetailRow({ required IconData icon, required String title, required String value, required MotorSignalThemeColors colors, required ThemeData theme, }) { return Row( children: [ Icon(icon, size: 18, color: colors.cyberCyan), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11.5, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 2), Text( value, style: const TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13.5, fontWeight: FontWeight.w600, ), ), ], ), ), ], ); } Widget _buildPill({ required IconData icon, required String label, required MotorSignalThemeColors colors, required ThemeData theme, Color? tintColor, }) { final effectiveColor = tintColor ?? theme.colorScheme.onSurfaceVariant; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: colors.surfaceContainerHighest.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(8), border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.6), width: 0.6), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 13, color: effectiveColor), const SizedBox(width: 5), Text( label, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11.5, fontWeight: FontWeight.w600, color: effectiveColor, ), ), ], ), ); } Widget _buildClubRidesCard( BuildContext context, WidgetRef ref, Club club, ClubMember? member, MotorSignalThemeColors colors, ThemeData theme, ) { final isActiveMember = member != null && member.status.isActive; final canPlanRides = isActiveMember && member.role.isAtLeast(ClubRole.admin); return Container( padding: const EdgeInsets.all(18.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(16), border: Border.all(color: colors.outlineVariant, width: 0.8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'CLUB RIDES', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w700, letterSpacing: 0.2, color: colors.cyberCyan, ), ), if (canPlanRides) ElevatedButton.icon( onPressed: () => context.push('/group-rides/create?clubId=${club.id}'), icon: const Icon(Icons.add_road, size: 14), label: const Text('Plan Club Ride'), style: ElevatedButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), textStyle: const TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11.5, fontWeight: FontWeight.w700, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ], ), const SizedBox(height: 12), if (!isActiveMember) Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: Text( 'Club rides are visible to active members only.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, color: theme.colorScheme.onSurfaceVariant, ), ), ) else _buildClubDiscoveryList(context, ref, club.id, colors, theme), ], ), ); } Widget _buildClubDiscoveryList( BuildContext context, WidgetRef ref, String clubId, MotorSignalThemeColors colors, ThemeData theme, ) { final ridesAsync = ref.watch(clubDiscoveryRidesProvider(clubId)); return ridesAsync.when( loading: () => const Padding( padding: EdgeInsets.symmetric(vertical: 24.0), child: Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ), ), error: (err, _) { String message = 'Unable to load club rides at this time.'; if (err is ClubOperationException) { switch (err.code) { case 'unauthenticated': message = 'Please sign in to view club rides.'; break; case 'permission_denied': message = 'Active club membership is required to view club rides.'; break; case 'timeout': case 'network_error': message = 'Network connection issue. Please check your connection and try again.'; break; default: message = 'Unable to load club rides at this time.'; } } return Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( message, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, color: theme.colorScheme.error, ), ), const SizedBox(height: 8), OutlinedButton.icon( onPressed: () => ref.invalidate(clubDiscoveryRidesProvider(clubId)), icon: const Icon(Icons.refresh, size: 14), label: const Text('Retry'), style: OutlinedButton.styleFrom( foregroundColor: colors.cyberCyan, side: BorderSide(color: colors.cyberCyan.withValues(alpha: 0.5)), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), textStyle: const TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), ], ), ); }, data: (rides) { if (rides.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: Row( children: [ Icon(Icons.two_wheeler, size: 18, color: theme.colorScheme.onSurfaceVariant), const SizedBox(width: 8), Expanded( child: Text( 'No upcoming club rides scheduled.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, color: theme.colorScheme.onSurfaceVariant, ), ), ), ], ), ); } final dateFormat = DateFormat('EEE, MMM d • h:mm a'); return Column( children: rides.map((ride) { final startLabel = dateFormat.format(ride.scheduledStartAt.toLocal()); final locationLabel = ride.meetingLocationLabel ?? 'Meeting area shared with participants'; return Padding( padding: const EdgeInsets.only(top: 10.0), child: Material( color: colors.surfaceContainerHighest.withValues(alpha: 0.4), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => context.push('/group-rides/${ride.rideId}'), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: colors.outlineVariant.withValues(alpha: 0.4), width: 0.8, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( ride.title, style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13.5, fontWeight: FontWeight.w700, color: theme.colorScheme.onSurface, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: colors.cyberCyan.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(4), border: Border.all( color: colors.cyberCyan.withValues(alpha: 0.3), width: 0.5, ), ), child: Text( ride.joinPolicy == 'open' ? 'OPEN' : (ride.joinPolicy == 'requestToJoin' ? 'REQUEST TO JOIN' : 'INVITE ONLY'), style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 9, fontWeight: FontWeight.w700, color: colors.cyberCyan, ), ), ), ], ), const SizedBox(height: 6), Row( children: [ Icon(Icons.calendar_today, size: 12, color: colors.cyberCyan), const SizedBox(width: 5), Text( startLabel, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11.5, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 4), Row( children: [ Icon(Icons.location_on_outlined, size: 12, color: theme.colorScheme.onSurfaceVariant), const SizedBox(width: 5), Expanded( child: Text( locationLabel, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 11.5, color: theme.colorScheme.onSurfaceVariant, ), ), ), ], ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: [ _buildPill( icon: Icons.group, label: '${ride.participantCount}${ride.participantLimit != null ? ' / ${ride.participantLimit}' : ''} riders', colors: colors, theme: theme, ), if (ride.paceClassification != null && ride.paceClassification!.isNotEmpty) _buildPill( icon: Icons.speed, label: ride.paceClassification!, colors: colors, theme: theme, ), if (ride.organizerDisplayName != null || ride.organizerHandle != null) _buildPill( icon: Icons.person_pin, label: ride.organizerDisplayName ?? ride.organizerHandle!, colors: colors, theme: theme, ), ], ), ], ), ), ), ), ); }).toList(), ); }, ); } }