import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/features/profile/presentation/providers/profile_provider.dart'; import 'package:real_estate_mobile/features/profile/presentation/screens/stripe_checkout_screen.dart'; class BillingTab extends ConsumerStatefulWidget { const BillingTab({super.key}); @override ConsumerState createState() => _BillingTabState(); } class _BillingTabState extends ConsumerState { Map? _subscription; bool _isLoadingSubscription = false; @override void initState() { super.initState(); _loadSubscription(); } Future _loadSubscription() async { if (_isLoadingSubscription) return; setState(() => _isLoadingSubscription = true); try { final repo = ref.read(profileRepositoryProvider); final sub = await repo.getSubscription(); setState(() { _subscription = sub; _isLoadingSubscription = false; }); } catch (_) { setState(() => _isLoadingSubscription = false); } } @override Widget build(BuildContext context) { if (_isLoadingSubscription) { return const Center( child: CircularProgressIndicator(color: AppColors.accentOrange)); } final hasActive = _subscription != null && _subscription!['status'] == 'ACTIVE'; final plan = _subscription?['plan'] as Map?; return ListView( padding: const EdgeInsets.fromLTRB(24, 10, 24, 30), children: [ const Center( child: Text( 'Subscription & Payments', style: TextStyle( fontFamily: 'Fractul', fontSize: 25, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), ), const SizedBox(height: 10), const Center( child: Text( 'Manage Your billing cycles , upgrade your\nplan , or boost individual listings', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), ), const SizedBox(height: 24), if (hasActive) ...[ const Text( 'Current Plan', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), _buildActiveSubscriptionCard(plan), ] else ...[ const Text( 'Annual Membership Plans', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), _buildPlanCard(), ], const SizedBox(height: 24), _buildNeedHelpSection(), ], ); } Widget _buildActiveSubscriptionCard(Map? plan) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.5), width: 0.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: AppColors.accentOrange, borderRadius: BorderRadius.circular(15), ), child: Text( plan?['name'] as String? ?? 'Annual Subscription', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: Colors.white, ), ), ), const SizedBox(height: 20), Row( children: [ const Icon(Icons.check_circle, color: Color(0xFF4CAF50), size: 20), const SizedBox(width: 8), const Text( 'Active', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF4CAF50), ), ), const Spacer(), if (_subscription?['currentPeriodEnd'] != null) Text( 'Renews ${_formatDate(_subscription!['currentPeriodEnd'] as String)}', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 13, color: AppColors.primaryDark.withValues(alpha: 0.6), ), ), ], ), if (plan?['amount'] != null) ...[ const SizedBox(height: 16), RichText( text: TextSpan( children: [ TextSpan( text: '\$${plan!['amount']}', style: const TextStyle( fontFamily: 'Fractul', fontSize: 35, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const TextSpan( text: ' / ', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), TextSpan( text: plan['interval'] as String? ?? 'Year', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark.withValues(alpha: 0.5), ), ), ], ), ), ], const SizedBox(height: 20), SizedBox( width: 155, child: ElevatedButton( onPressed: _openBillingPortal, style: ElevatedButton.styleFrom( backgroundColor: AppColors.accentOrange, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15)), ), child: const Text('Manage Subscription', style: TextStyle( fontFamily: 'Fractul', fontSize: 13, fontWeight: FontWeight.w700, color: Colors.white)), ), ), const SizedBox(height: 24), const Text( 'Include Features', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), ..._buildPlanFeatures(), const SizedBox(height: 16), Center( child: TextButton( onPressed: _cancelSubscription, style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Cancel Subscription', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w500)), ), ), ], ), ); } Widget _buildPlanCard() { return Stack( clipBehavior: Clip.none, children: [ Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.5), width: 0.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: AppColors.accentOrange, borderRadius: BorderRadius.circular(15), ), child: const Text( 'Annual Subscription', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: Colors.white, ), ), ), const SizedBox(height: 24), const Text( 'Professional Annual Plan', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 8), const Text( 'Complete solutions for real estate\nagents and lenders to grow their\nbusiness.', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), const SizedBox(height: 20), RichText( text: TextSpan( children: [ const TextSpan( text: '\$499', style: TextStyle( fontFamily: 'Fractul', fontSize: 35, fontWeight: FontWeight.w700, color: Colors.black, ), ), const TextSpan( text: ' / ', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: Colors.black, ), ), TextSpan( text: 'Year', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: Colors.black.withValues(alpha: 0.5), ), ), ], ), ), const SizedBox(height: 20), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 155, height: 43, child: ElevatedButton( onPressed: _startCheckout, style: ElevatedButton.styleFrom( backgroundColor: AppColors.accentOrange, side: const BorderSide(color: AppColors.accentOrange), padding: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15)), ), child: const Text('Get Premium Access', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white)), ), ), const Spacer(), ClipRRect( borderRadius: BorderRadius.circular(70), child: Image.asset( 'assets/icons/wallet_illustration.jpg', width: 120, height: 120, fit: BoxFit.cover, ), ), ], ), const SizedBox(height: 16), const Text( 'Include Features', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), ..._buildPlanFeatures(), ], ), ), Positioned( top: 0, right: 0, child: ClipRRect( borderRadius: const BorderRadius.only( topRight: Radius.circular(10), ), child: SizedBox( width: 62, height: 134, child: Stack( children: [ CustomPaint( size: const Size(62, 134), painter: _RibbonPainter(), ), // White dot at top of ribbon Positioned( top: 5, left: 10, child: Container( width: 14, height: 14, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), ), ), // "Best value" text centered on the diagonal Positioned.fill( child: Center( child: Transform.rotate( angle: 1.17, // ~67 degrees child: const Text( 'Best value', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Fractul', fontSize: 14, color: Colors.white, ), ), ), ), ), ], ), ), ), ), ], ); } Widget _buildNeedHelpSection() { return Container( clipBehavior: Clip.antiAlias, padding: const EdgeInsets.fromLTRB(20, 30, 20, 0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.7), width: 0.7, ), ), child: Column( children: [ const Text( 'Need Help ?', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 10), const Text( 'Our billing experts are here to help you\nwith any questions\n about your plan.', textAlign: TextAlign.center, style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), GestureDetector( onTap: () async { final uri = Uri.parse('mailto:support@re-quest.co'); if (await canLaunchUrl(uri)) { await launchUrl(uri); } }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'support@example.com', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.accentOrange, decoration: TextDecoration.underline, decorationColor: AppColors.accentOrange, ), ), const SizedBox(width: 4), Transform.rotate( angle: 0.72, child: const Icon(Icons.arrow_upward, size: 16, color: AppColors.accentOrange), ), ], ), ), const SizedBox(height: 20), ClipRRect( borderRadius: BorderRadius.circular(57), child: Image.asset( 'assets/icons/need_help_illustration.jpg', width: 261, height: 261, fit: BoxFit.cover, ), ), ], ), ); } List _buildPlanFeatures() { const features = [ ('Agent -Co-Marketing', true), ('Priority 24/7 Support', false), ('Whitelabel Client Portals', false), ('Advanced CRM Tools & Analytics', false), ]; return features .map((f) => Padding( padding: const EdgeInsets.only(bottom: 20), child: Row( children: [ SvgPicture.asset( f.$2 ? 'assets/icons/diamond_icon.svg' : 'assets/icons/feature_check_icon.svg', width: 18, height: 18, ), const SizedBox(width: 12), Text(f.$1, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark)), ], ), )) .toList(); } Future _openBillingPortal() async { try { final repo = ref.read(profileRepositoryProvider); final url = await repo.createPortalSession(); if (!mounted) return; await Navigator.of(context).push?>( MaterialPageRoute( builder: (_) => StripeCheckoutScreen( url: url, title: 'Manage Subscription', ), ), ); if (mounted) { _loadSubscription(); } } catch (e) { if (mounted) _showSnackBar('Failed to open billing portal: $e', isError: true); } } Future _startCheckout() async { try { final repo = ref.read(profileRepositoryProvider); final plans = await repo.getStripePlans(); if (plans.isEmpty) { _showSnackBar('No plans available', isError: true); return; } final planId = plans.first['id'] as String; final url = await repo.createCheckoutSession(planId); if (!mounted) return; final result = await Navigator.of(context).push?>( MaterialPageRoute( builder: (_) => StripeCheckoutScreen( url: url, title: 'Checkout', successUrlPattern: 'confirmation', ), ), ); if (result != null && result['success'] == true && mounted) { final sessionId = result['sessionId'] as String?; context.go('/payment-success${sessionId != null ? '?session_id=$sessionId' : ''}'); } else if (mounted) { _loadSubscription(); } } catch (e) { if (mounted) _showSnackBar('Failed to start checkout: $e', isError: true); } } Future _cancelSubscription() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Cancel Subscription', style: TextStyle( fontFamily: 'Fractul', fontWeight: FontWeight.w700, color: AppColors.primaryDark)), content: const Text( 'Are you sure you want to cancel your subscription? You will lose access to premium features at the end of the billing period.', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Keep Subscription')), TextButton( onPressed: () => Navigator.pop(ctx, true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Yes, Cancel'), ), ], ), ); if (confirmed != true || !mounted) return; try { final repo = ref.read(profileRepositoryProvider); await repo.cancelSubscription(); _showSnackBar('Subscription cancelled'); _loadSubscription(); } catch (e) { if (mounted) _showSnackBar('Failed to cancel: $e', isError: true); } } String _formatDate(String dateStr) { try { final date = DateTime.parse(dateStr); return DateFormat('MMM d, y').format(date); } catch (_) { return dateStr; } } void _showSnackBar(String message, {bool isError = false}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? Colors.red : const Color(0xFF638559), ), ); } } class _RibbonPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = AppColors.accentOrange; final w = size.width; final h = size.height; final path = Path() ..moveTo(w * (28.5 / 62), 0) ..lineTo(0, 0) ..lineTo(w, h) ..lineTo(w, h * (73.99 / 134)) ..close(); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }