diff --git a/lib/helpers/services/api_endpoints.dart b/lib/helpers/services/api_endpoints.dart index c0c360b..61ec66c 100644 --- a/lib/helpers/services/api_endpoints.dart +++ b/lib/helpers/services/api_endpoints.dart @@ -1,6 +1,6 @@ class ApiEndpoints { - // static const String baseUrl = "https://stageapi.marcoaiot.com/api"; - static const String baseUrl = "https://api.marcoaiot.com/api"; + static const String baseUrl = "https://stageapi.marcoaiot.com/api"; + // static const String baseUrl = "https://api.marcoaiot.com/api"; // static const String baseUrl = "https://devapi.marcoaiot.com/api"; // Dashboard Module API Endpoints @@ -110,4 +110,5 @@ class ApiEndpoints { static const String tenantSubscribe = '/Tenant/self/subscription'; static const String tenantRenewSubscription = '/Tenant/renew/subscription'; static const String getIndustries = '/market/industries'; + static const String getCurrentSubscription = '/subscription/current'; } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 17e2995..099ce23 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -2323,6 +2323,22 @@ class ApiService { } } + /// Get currently active subscription for logged-in tenant/user + static Future?> getCurrentSubscription() async { + try { + final response = await _getRequest( + ApiEndpoints.getCurrentSubscription, + requireAuth: true, // likely requires auth + ); + if (response == null) return null; + return _parseResponse(response, label: "Get Current Subscription"); + } catch (e) { + logSafe("❌ Exception in getCurrentSubscription: $e", + level: LogLevel.error); + return null; + } + } + // === Employee APIs === /// Search employees by first name and last name only (not middle name) /// Returns a list of up to 10 employee records matching the search string. diff --git a/lib/routes.dart b/lib/routes.dart index 090738f..3648aac 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -24,6 +24,7 @@ import 'package:marco/view/tenant/tenant_selection_screen.dart'; import 'package:marco/view/payment/payment_screen.dart'; import 'package:marco/view/subscriptions/subscriptions_screen.dart'; import 'package:marco/view/tenant/tenant_create_screen.dart'; +import 'package:marco/view/subscriptions/current_plan_screen.dart'; class AuthMiddleware extends GetMiddleware { @override @@ -104,6 +105,11 @@ getPageRoute() { name: '/subscription', page: () => SubscriptionScreen(), ), + + GetPage( + name: '/subscription/current', + page: () => const CurrentPlanScreen(), + ), // Authentication GetPage(name: '/auth/login', page: () => LoginScreen()), GetPage(name: '/auth/login-option', page: () => LoginOptionScreen()), diff --git a/lib/view/layouts/user_profile_right_bar.dart b/lib/view/layouts/user_profile_right_bar.dart index 7b19564..de2fcb5 100644 --- a/lib/view/layouts/user_profile_right_bar.dart +++ b/lib/view/layouts/user_profile_right_bar.dart @@ -318,7 +318,7 @@ class _UserProfileBarState extends State SizedBox(height: spacingHeight), _menuItemRow( icon: LucideIcons.bell, - label: 'Subscribe', + label: 'Subscription', onTap: _onSubscribeTap, iconColor: Colors.redAccent, textColor: Colors.redAccent, @@ -394,7 +394,7 @@ class _UserProfileBarState extends State } void _onSubscribeTap() { - Get.toNamed("/subscription"); + Get.toNamed("/subscription/current"); } diff --git a/lib/view/subscriptions/current_plan_screen.dart b/lib/view/subscriptions/current_plan_screen.dart new file mode 100644 index 0000000..ad66f2c --- /dev/null +++ b/lib/view/subscriptions/current_plan_screen.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:marco/helpers/services/api_service.dart'; +import 'package:marco/helpers/services/app_logger.dart'; + +class CurrentPlanScreen extends StatefulWidget { + const CurrentPlanScreen({super.key}); + + @override + State createState() => _CurrentPlanScreenState(); +} + +class _CurrentPlanScreenState extends State { + bool _loading = true; + Map? _plan; + + @override + void initState() { + super.initState(); + _fetchCurrentPlan(); + } + + Future _fetchCurrentPlan() async { + try { + setState(() => _loading = true); + final resp = await ApiService.getCurrentSubscription(); + logSafe("💡 Get Current Subscription Response: $resp"); + if (resp == null || resp['success'] != true) { + setState(() { + _plan = null; + _loading = false; + }); + return; + } + final data = resp['data'] ?? resp; + setState(() { + _plan = data is Map ? Map.from(data) : null; + _loading = false; + }); + } catch (e, s) { + logSafe("❌ Exception while fetching current plan: $e\n$s", level: LogLevel.error); + if (mounted) setState(() => _loading = false); + } + } + + Widget _buildPlaceholder() { + return Center( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + const Icon(Icons.info_outline, size: 48, color: Colors.grey), + const SizedBox(height: 12), + const Text("No active subscription", style: TextStyle(fontSize: 16, color: Colors.grey)), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () => Get.toNamed('/subscription'), + child: const Text("View Plans"), + ), + ]), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Current Plan", style: TextStyle(fontWeight: FontWeight.w600, color: Colors.black)), + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new), + onPressed: () => Get.back(), + ), + ), + body: _loading + ? const Center(child: CircularProgressIndicator()) + : Padding( + padding: const EdgeInsets.all(16), + child: _plan == null + ? _buildPlaceholder() + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + _planDisplayName(_plan!), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text(_plan!['description'] ?? '', style: const TextStyle(color: Colors.grey)), + const SizedBox(height: 12), + Row( + children: [ + Text("Price: ", style: TextStyle(color: Colors.grey[700])), + Text("${_currencySymbol(_plan!)}${_plan!['price'] ?? 0}", style: const TextStyle(fontWeight: FontWeight.bold)), + const Spacer(), + Text("Trial: ${_plan!['trialDays'] ?? 0} days", style: const TextStyle(color: Colors.grey)), + ], + ), + const SizedBox(height: 12), + if (_plan!['startedAt'] != null) + Text("Started: ${_plan!['startedAt']}", style: const TextStyle(color: Colors.grey)), + if (_plan!['expiresAt'] != null) + Text("Expires: ${_plan!['expiresAt']}", style: const TextStyle(color: Colors.grey)), + ]), + ), + ), + + const SizedBox(height: 16), + + // Features / modules (if present) + if (_plan!['features'] != null) + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildFeaturesList(_plan!), + ), + ), + ) + else + const SizedBox.shrink(), + + const SizedBox(height: 16), + + ElevatedButton( + onPressed: () { + // navigate to subscription page (renew flow) + // pass current plan id so subscription screen can preselect if needed + Get.toNamed('/subscription', arguments: {'currentPlanId': _plan!['id']}); + }, + style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 14)), + child: const Text("Renew Plan"), + ), + ], + ), + ), + ); + } + + String _planDisplayName(Map plan) { + return plan['planName'] ?? plan['name'] ?? 'Plan'; + } + + String _currencySymbol(Map plan) { + try { + return plan['currency']?['symbol'] ?? '₹'; + } catch (_) { + return '₹'; + } + } + + List _buildFeaturesList(Map plan) { + final features = []; + try { + final modules = plan['features']?['modules'] ?? {}; + if (modules is Map) { + modules.forEach((k, v) { + if (v is Map && v['enabled'] == true) features.add(v['name'] ?? k); + }); + } + final supports = plan['features']?['supports'] ?? {}; + if (supports is Map) { + supports.forEach((k, v) { + if (v == true) features.add(k.toString().replaceAll(RegExp(r'([a-z])([A-Z])'), r'\1 \2')); + }); + } + } catch (_) {} + if (features.isEmpty) return [const SizedBox.shrink()]; + return [ + const SizedBox(height: 12), + const Text("Included features", style: TextStyle(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + ...features.map((f) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row(children: [const Icon(Icons.check, size: 16, color: Colors.green), const SizedBox(width: 8), Expanded(child: Text(f))]), + )), + ]; + } +}