diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_en.arb b/hesabixUI/hesabix_ui/lib/l10n/app_en.arb index bbbe85a..357fde7 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_en.arb +++ b/hesabixUI/hesabix_ui/lib/l10n/app_en.arb @@ -48,5 +48,11 @@ , "ok": "OK", "cancel": "Cancel" + , + "newBusiness": "New business", + "businesses": "Businesses", + "support": "Support", + "changePassword": "Change password", + "marketing": "Marketing" } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb b/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb index f2301fe..0d5c021 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb +++ b/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb @@ -46,6 +46,11 @@ "logoutConfirmMessage": "آیا برای خروج مطمئن هستید؟", "menu": "منو" , + "newBusiness": "کسب‌وکار جدید", + "businesses": "کسب‌وکارها", + "support": "پشتیبانی", + "changePassword": "تغییر کلمه عبور", + "marketing": "بازاریابی", "ok": "تایید", "cancel": "انصراف" } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart index dab86a5..bbaafa3 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart @@ -339,10 +339,46 @@ abstract class AppLocalizations { String get menu; /// No description provided for @ok. + /// + /// In en, this message translates to: + /// **'OK'** String get ok; /// No description provided for @cancel. + /// + /// In en, this message translates to: + /// **'Cancel'** String get cancel; + + /// No description provided for @newBusiness. + /// + /// In en, this message translates to: + /// **'New business'** + String get newBusiness; + + /// No description provided for @businesses. + /// + /// In en, this message translates to: + /// **'Businesses'** + String get businesses; + + /// No description provided for @support. + /// + /// In en, this message translates to: + /// **'Support'** + String get support; + + /// No description provided for @changePassword. + /// + /// In en, this message translates to: + /// **'Change password'** + String get changePassword; + + /// No description provided for @marketing. + /// + /// In en, this message translates to: + /// **'Marketing'** + String get marketing; } class _AppLocalizationsDelegate diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart index 73baac5..f49afd8 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart @@ -135,4 +135,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get cancel => 'Cancel'; + + @override + String get newBusiness => 'New business'; + + @override + String get businesses => 'Businesses'; + + @override + String get support => 'Support'; + + @override + String get changePassword => 'Change password'; + + @override + String get marketing => 'Marketing'; } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart index 39e4351..c6d076f 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart @@ -135,4 +135,19 @@ class AppLocalizationsFa extends AppLocalizations { @override String get cancel => 'انصراف'; + + @override + String get newBusiness => 'کسب‌وکار جدید'; + + @override + String get businesses => 'کسب‌وکارها'; + + @override + String get support => 'پشتیبانی'; + + @override + String get changePassword => 'تغییر کلمه عبور'; + + @override + String get marketing => 'بازاریابی'; } diff --git a/hesabixUI/hesabix_ui/lib/main.dart b/hesabixUI/hesabix_ui/lib/main.dart index 2b8f697..ebeebac 100644 --- a/hesabixUI/hesabix_ui/lib/main.dart +++ b/hesabixUI/hesabix_ui/lib/main.dart @@ -6,6 +6,11 @@ import 'pages/login_page.dart'; import 'pages/home_page.dart'; import 'pages/profile/profile_shell.dart'; import 'pages/profile/profile_dashboard_page.dart'; +import 'pages/profile/new_business_page.dart'; +import 'pages/profile/businesses_page.dart'; +import 'pages/profile/support_page.dart'; +import 'pages/profile/change_password_page.dart'; +import 'pages/profile/marketing_page.dart'; import 'package:hesabix_ui/l10n/app_localizations.dart'; import 'core/locale_controller.dart'; import 'core/api_client.dart'; @@ -113,6 +118,31 @@ class _MyAppState extends State { name: 'profile_dashboard', builder: (context, state) => const ProfileDashboardPage(), ), + GoRoute( + path: '/user/profile/new-business', + name: 'profile_new_business', + builder: (context, state) => const NewBusinessPage(), + ), + GoRoute( + path: '/user/profile/businesses', + name: 'profile_businesses', + builder: (context, state) => const BusinessesPage(), + ), + GoRoute( + path: '/user/profile/support', + name: 'profile_support', + builder: (context, state) => const SupportPage(), + ), + GoRoute( + path: '/user/profile/marketing', + name: 'profile_marketing', + builder: (context, state) => const MarketingPage(), + ), + GoRoute( + path: '/user/profile/change-password', + name: 'profile_change_password', + builder: (context, state) => const ChangePasswordPage(), + ), ], ), ], diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/businesses_page.dart b/hesabixUI/hesabix_ui/lib/pages/profile/businesses_page.dart new file mode 100644 index 0000000..10a6557 --- /dev/null +++ b/hesabixUI/hesabix_ui/lib/pages/profile/businesses_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hesabix_ui/l10n/app_localizations.dart'; + +class BusinessesPage extends StatelessWidget { + const BusinessesPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = AppLocalizations.of(context); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.businesses, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('${t.businesses} - sample page'), + ], + ), + ); + } +} + + diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/change_password_page.dart b/hesabixUI/hesabix_ui/lib/pages/profile/change_password_page.dart new file mode 100644 index 0000000..f7b3b1a --- /dev/null +++ b/hesabixUI/hesabix_ui/lib/pages/profile/change_password_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hesabix_ui/l10n/app_localizations.dart'; + +class ChangePasswordPage extends StatelessWidget { + const ChangePasswordPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = AppLocalizations.of(context); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.changePassword, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('${t.changePassword} - sample page'), + ], + ), + ); + } +} + + diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/marketing_page.dart b/hesabixUI/hesabix_ui/lib/pages/profile/marketing_page.dart new file mode 100644 index 0000000..2f971f2 --- /dev/null +++ b/hesabixUI/hesabix_ui/lib/pages/profile/marketing_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hesabix_ui/l10n/app_localizations.dart'; + +class MarketingPage extends StatelessWidget { + const MarketingPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = AppLocalizations.of(context); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.marketing, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('${t.marketing} - sample page'), + ], + ), + ); + } +} + + diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/new_business_page.dart b/hesabixUI/hesabix_ui/lib/pages/profile/new_business_page.dart new file mode 100644 index 0000000..1b0242c --- /dev/null +++ b/hesabixUI/hesabix_ui/lib/pages/profile/new_business_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hesabix_ui/l10n/app_localizations.dart'; + +class NewBusinessPage extends StatelessWidget { + const NewBusinessPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = AppLocalizations.of(context); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.newBusiness, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('${t.newBusiness} - sample page'), + ], + ), + ); + } +} + + diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/profile_shell.dart b/hesabixUI/hesabix_ui/lib/pages/profile/profile_shell.dart index 2630b15..897a022 100644 --- a/hesabixUI/hesabix_ui/lib/pages/profile/profile_shell.dart +++ b/hesabixUI/hesabix_ui/lib/pages/profile/profile_shell.dart @@ -8,13 +8,20 @@ import '../../widgets/theme_mode_switcher.dart'; import '../../widgets/logout_button.dart'; import 'package:hesabix_ui/l10n/app_localizations.dart'; -class ProfileShell extends StatelessWidget { +class ProfileShell extends StatefulWidget { final Widget child; final AuthStore authStore; final LocaleController? localeController; final ThemeController? themeController; const ProfileShell({super.key, required this.child, required this.authStore, this.localeController, this.themeController}); + @override + State createState() => _ProfileShellState(); +} + +class _ProfileShellState extends State { + int _hoverIndex = -1; + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -22,10 +29,19 @@ class ProfileShell extends StatelessWidget { final bool railExtended = width >= 1100; final ColorScheme scheme = Theme.of(context).colorScheme; final String location = GoRouterState.of(context).uri.toString(); + final bool isDark = Theme.of(context).brightness == Brightness.dark; + final String logoAsset = isDark + ? 'assets/images/logo-light.png' + : 'assets/images/logo-light.png'; final t = AppLocalizations.of(context); final destinations = <_Dest>[ _Dest(t.dashboard, Icons.dashboard_outlined, Icons.dashboard, '/user/profile/dashboard'), + _Dest(t.newBusiness, Icons.add_business, Icons.add_business, '/user/profile/new-business'), + _Dest(t.businesses, Icons.business, Icons.business, '/user/profile/businesses'), + _Dest(t.support, Icons.support_agent, Icons.support_agent, '/user/profile/support'), + _Dest(t.marketing, Icons.campaign, Icons.campaign, '/user/profile/marketing'), + _Dest(t.changePassword, Icons.password, Icons.password, '/user/profile/change-password'), ]; int selectedIndex = 0; @@ -44,7 +60,7 @@ class ProfileShell extends StatelessWidget { } Future onLogout() async { - await authStore.saveApiKey(null); + await widget.authStore.saveApiKey(null); if (!context.mounted) return; ScaffoldMessenger.of(context) ..hideCurrentSnackBar() @@ -67,12 +83,9 @@ class ProfileShell extends StatelessWidget { title: Row( children: [ const SizedBox(width: 12), - // Logo placeholder (can replace with AssetImage) - CircleAvatar(backgroundColor: appBarFg.withOpacity(0.15), child: Icon(Icons.account_balance, color: appBarFg)), + Image.asset(logoAsset, height: 28), const SizedBox(width: 12), Text(t.appTitle, style: TextStyle(color: appBarFg, fontWeight: FontWeight.w700)), - const SizedBox(width: 8), - Text(destinations[selectedIndex].label, style: TextStyle(color: appBarFg.withOpacity(0.85))), ], ), leading: useRail @@ -85,15 +98,15 @@ class ProfileShell extends StatelessWidget { ), ), actions: [ - if (themeController != null) ...[ - ThemeModeSwitcher(controller: themeController!), + if (widget.themeController != null) ...[ + ThemeModeSwitcher(controller: widget.themeController!), const SizedBox(width: 8), ], - if (localeController != null) ...[ - LanguageSwitcher(controller: localeController!), + if (widget.localeController != null) ...[ + LanguageSwitcher(controller: widget.localeController!), const SizedBox(width: 8), ], - LogoutButton(authStore: authStore), + LogoutButton(authStore: widget.authStore), ], ); @@ -102,35 +115,75 @@ class ProfileShell extends StatelessWidget { child: SafeArea( child: Padding( padding: const EdgeInsets.all(16), - child: child, + child: widget.child, ), ), ); + // Side colors and styles + final Color sideBg = Theme.of(context).brightness == Brightness.dark + ? scheme.surfaceContainerHighest + : scheme.surfaceContainerLow; + final Color sideFg = scheme.onSurfaceVariant; + final Color activeBg = scheme.primaryContainer; + final Color activeFg = scheme.onPrimaryContainer; + if (useRail) { return Scaffold( appBar: appBar, body: Row( children: [ - NavigationRail( - selectedIndex: selectedIndex, - extended: railExtended, - leading: Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: CircleAvatar( - backgroundColor: scheme.primary, - child: const Icon(Icons.person, color: Colors.white), - ), + Container( + width: railExtended ? 240 : 88, + height: double.infinity, + color: sideBg, + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: destinations.length, + itemBuilder: (ctx, i) { + final d = destinations[i]; + final bool isHovered = i == _hoverIndex; + final bool isSelected = i == selectedIndex; + final bool active = isSelected || isHovered; + final double radius = isHovered ? 0 : 8; + return MouseRegion( + onEnter: (_) => setState(() => _hoverIndex = i), + onExit: (_) => setState(() => _hoverIndex = -1), + child: InkWell( + borderRadius: BorderRadius.circular(radius), + onTap: () => onSelect(i), + child: Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: railExtended ? 12 : 0, vertical: 10), + decoration: BoxDecoration( + color: active ? activeBg : Colors.transparent, + borderRadius: BorderRadius.circular(radius), + ), + child: Row( + mainAxisAlignment: railExtended ? MainAxisAlignment.start : MainAxisAlignment.center, + children: [ + Icon(d.icon, color: active ? activeFg : sideFg), + if (railExtended) ...[ + const SizedBox(width: 12), + Expanded( + child: Text( + d.label, + style: TextStyle( + color: active ? activeFg : sideFg, + fontWeight: active ? FontWeight.w600 : FontWeight.w400, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ], + ), + ), + ), + ); + }, ), - destinations: [ - for (final d in destinations) - NavigationRailDestination( - icon: Icon(d.icon), - selectedIcon: Icon(d.selectedIcon), - label: Text(d.label), - ), - ], - onDestinationSelected: onSelect, ), const VerticalDivider(width: 1), Expanded(child: content), @@ -142,31 +195,31 @@ class ProfileShell extends StatelessWidget { return Scaffold( appBar: appBar, drawer: Drawer( + backgroundColor: sideBg, child: SafeArea( - child: Column( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 8), children: [ - UserAccountsDrawerHeader( - currentAccountPicture: CircleAvatar( - backgroundColor: scheme.primary, - child: const Icon(Icons.person, color: Colors.white), - ), - accountName: const Text(''), - accountEmail: const Text(''), - ), - for (int i = 0; i < destinations.length; i++) - ListTile( - leading: Icon(destinations[i].selectedIcon), - title: Text(destinations[i].label), - selected: i == selectedIndex, - onTap: () { - Navigator.of(context).pop(); - onSelect(i); - }, - ), - const Spacer(), + for (int i = 0; i < destinations.length; i++) ...[ + Builder(builder: (ctx) { + final d = destinations[i]; + final bool active = i == selectedIndex; + return ListTile( + leading: Icon(d.selectedIcon, color: active ? activeFg : sideFg), + title: Text(d.label, style: TextStyle(color: active ? activeFg : sideFg, fontWeight: active ? FontWeight.w600 : FontWeight.w400)), + selected: active, + selectedTileColor: activeBg, + onTap: () { + Navigator.of(context).pop(); + onSelect(i); + }, + ); + }), + ], + const Divider(), ListTile( leading: const Icon(Icons.logout), - title: const Text('خروج'), + title: Text(t.logout), onTap: onLogout, ), ], diff --git a/hesabixUI/hesabix_ui/lib/pages/profile/support_page.dart b/hesabixUI/hesabix_ui/lib/pages/profile/support_page.dart new file mode 100644 index 0000000..30355c3 --- /dev/null +++ b/hesabixUI/hesabix_ui/lib/pages/profile/support_page.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hesabix_ui/l10n/app_localizations.dart'; + +class SupportPage extends StatelessWidget { + const SupportPage({super.key}); + + @override + Widget build(BuildContext context) { + final t = AppLocalizations.of(context); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.support, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('${t.support} - sample page'), + ], + ), + ); + } +} + +