Pricing page implemented.

This commit is contained in:
bala 2025-11-29 22:28:02 +05:30
parent a978699dfc
commit 6e9ffdb9dd
18 changed files with 692 additions and 206 deletions

View File

@ -5,6 +5,7 @@ import 'package:autos/presentation/screens/auth/sign_up_screen.dart';
import 'package:autos/presentation/screens/brands/brands_screen.dart'; import 'package:autos/presentation/screens/brands/brands_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart'; import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
import 'package:autos/presentation/screens/ebay/ebay_screen.dart'; import 'package:autos/presentation/screens/ebay/ebay_screen.dart';
import 'package:autos/presentation/screens/pricing/pricing_screen.dart';
import 'package:autos/presentation/screens/products/products_screen.dart'; import 'package:autos/presentation/screens/products/products_screen.dart';
import 'package:autos/presentation/screens/store/create_location_screen.dart'; import 'package:autos/presentation/screens/store/create_location_screen.dart';
import 'package:autos/presentation/screens/store/store.dart'; import 'package:autos/presentation/screens/store/store.dart';
@ -65,6 +66,9 @@ class AppRouter {
case AppRoutePaths.products: case AppRoutePaths.products:
return slideRoute(ProductsScreen()); return slideRoute(ProductsScreen());
case AppRoutePaths.pricing:
return slideRoute(PricingScreen());
default: default:
return _defaultFallback(settings); return _defaultFallback(settings);
} }

View File

@ -12,4 +12,5 @@ class AppRoutePaths {
static const createStoreLocation = '/createStoreLocation'; static const createStoreLocation = '/createStoreLocation';
static const brands = '/brands'; static const brands = '/brands';
static const products = '/products'; static const products = '/products';
static const pricing = '/pricing';
} }

View File

@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
class AppTheme {
// MAIN BRAND COLORS
static const Color primary = Color(0xFF00BFFF); // Main Blue
static const Color lightBlue = Color(0xFFE8F7FF); // Sidebar highlight
static const Color background = Color(0xFFF4FBFE); // App background
static const Color textDark = Color(0xFF1C1C1C);
static const Color textGrey = Color(0xFF6F6F6F);
static const Color cardBorder = Color(0xFFAEE9FF);
// GLOBAL APP THEME
static ThemeData lightTheme = ThemeData(
useMaterial3: true,
scaffoldBackgroundColor: background,
primaryColor: primary,
colorScheme: ColorScheme.fromSeed(
seedColor: primary,
brightness: Brightness.light,
),
fontFamily: 'Poppins',
// TEXT STYLES
textTheme: const TextTheme(
headlineLarge: TextStyle(
fontSize: 34,
fontWeight: FontWeight.bold,
color: textDark,
),
headlineMedium: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: textDark,
),
headlineSmall: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: textDark,
),
bodyLarge: TextStyle(fontSize: 16, color: textDark),
bodyMedium: TextStyle(fontSize: 14, color: textGrey),
),
// ELEVATED BUTTON THEME (SUBSCRIBE BUTTONS)
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primary,
foregroundColor: Colors.white,
elevation: 6,
shadowColor: Colors.black.withOpacity(0.2),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 14),
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
),
// OUTLINED BUTTON THEME (UNSELECTED BUTTONS)
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: primary,
side: const BorderSide(color: primary),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 14),
),
),
// INPUT FIELD THEME
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: cardBorder),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: cardBorder),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: primary, width: 2),
),
),
// CARD THEME (PRICING BOXES)
cardTheme: CardThemeData(
color: Colors.white,
elevation: 12,
shadowColor: Colors.black.withValues(alpha: 0.06),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(26)),
),
// APP BAR THEME
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: Colors.white),
titleTextStyle: TextStyle(
color: textDark,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
// DIVIDER THEME
dividerTheme: const DividerThemeData(color: cardBorder, thickness: 1),
// ICON THEME
iconTheme: const IconThemeData(color: primary),
);
}

View File

@ -13,15 +13,16 @@ class AppTypo {
); );
static const TextStyle h2 = TextStyle( static const TextStyle h2 = TextStyle(
color: Color(0xFF3B81F9), color: Color(0xFF00BFFF),
fontFamily: fontFamily, fontFamily: fontFamily,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
); );
static const TextStyle h3 = TextStyle( static const TextStyle h3 = TextStyle(
color: Colors.white,
fontFamily: fontFamily, fontFamily: fontFamily,
fontSize: 24, fontSize: 22,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );

View File

@ -9,7 +9,7 @@ class HamburgerButton extends StatefulWidget {
super.key, super.key,
required this.scaffoldKey, required this.scaffoldKey,
this.backgroundColor = const Color(0xFFEAF1FF), // light background this.backgroundColor = const Color(0xFFEAF1FF), // light background
this.iconColor = const Color(0xFF3B81F9), // dark blue lines this.iconColor = const Color(0xFF00BFFF), // dark blue lines
}); });
@override @override

View File

@ -22,7 +22,7 @@ class SideMenu extends ConsumerWidget {
children: [ children: [
// Header // Header
DrawerHeader( DrawerHeader(
decoration: BoxDecoration(color: Color(0xFF3B81F9)), decoration: BoxDecoration(color: Color(0xFF00BFFF)),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: Row( child: Row(
@ -71,7 +71,7 @@ class SideMenu extends ConsumerWidget {
// --- ACCOUNT --- // --- ACCOUNT ---
_sectionHeader("ACCOUNT"), _sectionHeader("ACCOUNT"),
_menuItem(context, "👤", "My Account", AppRoutePaths.auth), _menuItem(context, "👤", "My Account", AppRoutePaths.auth),
_menuItem(context, "💰", "Pricing Plan", 'pricing'), _menuItem(context, "💰", "Pricing Plan", AppRoutePaths.pricing),
], ],
), ),
), ),
@ -123,7 +123,7 @@ class SideMenu extends ConsumerWidget {
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 15, fontSize: 15,
letterSpacing: 0.5, letterSpacing: 0.5,
color: Color(0xFF3B81F9), color: Color(0xFF00BFFF),
), ),
), ),
), ),

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class WaveBackground extends StatelessWidget { class WaveBackground extends StatelessWidget {
@ -19,10 +20,7 @@ class WaveBackground extends StatelessWidget {
height: 220, height: 220,
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [AppTheme.primary, AppTheme.primary],
Color(0xFF3B81F9), // blue
Color(0xFF5A96F9), // lighter blue
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
@ -31,31 +29,6 @@ class WaveBackground extends StatelessWidget {
), ),
), ),
// Positioned(
// top: 50,
// left: 0,
// right: 0,
// child: Row(
// children: [
// Image.asset(
// 'assets/auth/ebay.png',
// height: 40,
// fit: BoxFit.contain,
// ),
// // Image.asset(
// // 'assets/auth/data.png',
// // height: 40,
// // fit: BoxFit.contain,
// // ),
// // Image.asset(
// // 'assets/auth/ebay.png',
// // height: 40,
// // fit: BoxFit.contain,
// // ),
// ],
// ),
// ),
// Optional bottom light curve (subtle, not dominant) // Optional bottom light curve (subtle, not dominant)
Positioned( Positioned(
bottom: 0, bottom: 0,
@ -65,7 +38,7 @@ class WaveBackground extends StatelessWidget {
clipper: BottomSoftWaveClipper(), clipper: BottomSoftWaveClipper(),
child: Container( child: Container(
height: 80, height: 80,
color: const Color(0xFF5A96F9), color: AppTheme.primary,
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(

View File

@ -1,5 +1,3 @@
import 'package:autos/domain/entities/product.dart'; import 'package:autos/domain/entities/product.dart';
class ProductModel extends ProductEntity { class ProductModel extends ProductEntity {
@ -12,10 +10,18 @@ class ProductModel extends ProductEntity {
factory ProductModel.fromJson(Map<String, dynamic> json) { factory ProductModel.fromJson(Map<String, dynamic> json) {
return ProductModel( return ProductModel(
id: json['id'], id: json['id'] ?? 0,
name: json['name'],
image: json['image'], /// SAFE STRING CONVERSION
price: (json['price'] as num).toDouble(), name: json['name']?.toString() ?? '',
/// API sends `logo`, but entity expects `image`
image: json['logo']?.toString() ?? '',
/// API DOES NOT SEND PRICE DEFAULT TO 0
price: json['price'] != null
? double.tryParse(json['price'].toString()) ?? 0.0
: 0.0,
); );
} }
} }

View File

@ -42,6 +42,8 @@ class BrandsRepositoryImpl implements BrandsRepository {
await _storage.write(key: 'turn14_token', value: token); await _storage.write(key: 'turn14_token', value: token);
} else if (data["code"] == "TOKEN_VALID") { } else if (data["code"] == "TOKEN_VALID") {
// Token still valid, read from storage // Token still valid, read from storage
token = data["access_token"];
await _storage.write(key: 'turn14_token', value: token);
token = await _storage.read(key: 'turn14_token') ?? ''; token = await _storage.read(key: 'turn14_token') ?? '';
if (token.isEmpty) { if (token.isEmpty) {
throw Exception("Token missing in storage"); throw Exception("Token missing in storage");

View File

@ -1,5 +1,6 @@
import 'package:autos/core/routing/app_router.dart'; import 'package:autos/core/routing/app_router.dart';
import 'package:autos/core/routing/navigation_service.dart'; import 'package:autos/core/routing/navigation_service.dart';
import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/presentation/providers/user_provider.dart'; import 'package:autos/presentation/providers/user_provider.dart';
import 'package:autos/presentation/screens/auth/login_screen.dart'; import 'package:autos/presentation/screens/auth/login_screen.dart';
import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart'; import 'package:autos/presentation/screens/dashboard/dashboard_screen.dart';
@ -52,9 +53,7 @@ class _MyAppState extends ConsumerState<MyApp> {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: "Autos", title: "Autos",
theme: ThemeData( theme: AppTheme.lightTheme,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
// Dynamic home based on session // Dynamic home based on session
home: user != null ? const DashboardScreen() : const LoginScreen(), home: user != null ? const DashboardScreen() : const LoginScreen(),

View File

@ -24,20 +24,28 @@ class BrandsNotifier extends StateNotifier<AsyncValue<List<BrandEntity>>> {
/// Fetch brands every time page opens /// Fetch brands every time page opens
Future<void> fetchBrands() async { Future<void> fetchBrands() async {
state = const AsyncValue.loading(); state = const AsyncValue.loading();
try { try {
final userAsync = ref.read(userDetailsProvider); /// READ USER DIRECTLY FROM userProvider (NOT userDetailsProvider)
final userAsync = ref.read(userProvider);
final user = userAsync.value; final user = userAsync.value;
if (user == null) throw Exception("User not logged in"); if (user == null || user.id.isEmpty) {
throw Exception("User id is empty");
}
debugPrint("✅ FETCHING BRANDS FOR USER ID: ${user.id}");
final brands = await repository.getBrands(); final brands = await repository.getBrands();
state = AsyncValue.data(brands); state = AsyncValue.data(brands);
} catch (e, st) { } catch (e, st) {
state = AsyncValue.error(e, st); state = AsyncValue.error(e, st);
debugPrint("Brands fetch error: $e"); debugPrint("❌ Brands fetch error: $e");
}
} }
}
/// SAVE SELECTED BRANDS TO SERVER (Bulk Insert) /// SAVE SELECTED BRANDS TO SERVER (Bulk Insert)
Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async { Future<void> saveSelectedBrands(List<BrandEntity> selectedBrands) async {

View File

@ -9,95 +9,131 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_riverpod/legacy.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// Provide a single ApiService instance across the app /// ------------------------------------------------------------
/// API SERVICE
/// ------------------------------------------------------------
final apiServiceProvider = Provider<ApiService>((ref) => ApiService()); final apiServiceProvider = Provider<ApiService>((ref) => ApiService());
// Provide repository that depends on ApiService /// ------------------------------------------------------------
/// USER REPOSITORY
/// ------------------------------------------------------------
final userRepositoryProvider = Provider<UserRepositoryImpl>( final userRepositoryProvider = Provider<UserRepositoryImpl>(
(ref) => UserRepositoryImpl(ref.read(apiServiceProvider)), (ref) => UserRepositoryImpl(ref.read(apiServiceProvider)),
); );
// Manage user state /// ------------------------------------------------------------
final loginProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>(( /// LOGIN PROVIDER (UNCHANGED NAME)
ref, /// ------------------------------------------------------------
) { final loginProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider); final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo); return UserNotifier(repo);
}); });
final signupProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>(( /// ------------------------------------------------------------
ref, /// SIGNUP PROVIDER (UNCHANGED NAME)
) { /// ------------------------------------------------------------
final signupProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider); final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo); return UserNotifier(repo);
}); });
final userProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>(( /// ------------------------------------------------------------
ref, /// MAIN USER PROVIDER (UNCHANGED NAME)
) { /// AUTO LOADS FROM STORAGE
/// ------------------------------------------------------------
final userProvider =
StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
final repo = ref.read(userRepositoryProvider); final repo = ref.read(userRepositoryProvider);
return UserNotifier(repo); final notifier = UserNotifier(repo);
/// AUTO RESTORE SESSION ON APP START
notifier.loadUserFromStorage();
return notifier;
}); });
/// ------------------------------------------------------------
/// USER DETAILS PROVIDER (UNCHANGED NAME)
/// ------------------------------------------------------------
final userDetailsProvider = FutureProvider<UserModel?>((ref) async { final userDetailsProvider = FutureProvider<UserModel?>((ref) async {
final userAsync = ref.watch(userProvider); final userAsync = ref.watch(userProvider);
// Waiting for login provider to finish
if (userAsync.isLoading) return null; if (userAsync.isLoading) return null;
final user = userAsync.value; final user = userAsync.value;
if (user == null) return null; if (user == null) return null;
// Fetch Full Details
final repo = ref.read(userRepositoryProvider); final repo = ref.read(userRepositoryProvider);
return await repo.getUserDetails(user.id); return await repo.getUserDetails(user.id);
}); });
/// ------------------------------------------------------------
/// AUTH STATE
/// ------------------------------------------------------------
enum AuthAction { idle, login, signup } enum AuthAction { idle, login, signup }
/// ------------------------------------------------------------
/// USER NOTIFIER
/// ------------------------------------------------------------
class UserNotifier extends StateNotifier<AsyncValue<User?>> { class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final UserRepositoryImpl repository; final UserRepositoryImpl repository;
final _storage = const FlutterSecureStorage(); final _storage = const FlutterSecureStorage();
static const _userKey = 'logged_in_user'; static const _userKey = 'logged_in_user';
AuthAction lastAction = AuthAction.idle; AuthAction lastAction = AuthAction.idle;
UserNotifier(this.repository) : super(const AsyncValue.data(null)); UserNotifier(this.repository) : super(const AsyncValue.data(null));
///Load saved user from storage (auto-login) /// LOAD USER FROM STORAGE (FIXED)
Future<void> loadUserFromStorage() async { Future<void> loadUserFromStorage() async {
try {
final jsonString = await _storage.read(key: _userKey); final jsonString = await _storage.read(key: _userKey);
if (jsonString != null) {
debugPrint("RESULT: $jsonString"); if (jsonString == null) {
debugPrint("🟡 No user found in storage");
return;
}
final jsonData = jsonDecode(jsonString); final jsonData = jsonDecode(jsonString);
final user = UserModel.fromJson(jsonData); final user = UserModel.fromJson(jsonData);
await Future.microtask(() {});
state = AsyncValue.data(user); state = AsyncValue.data(user);
debugPrint("✅ USER RESTORED → ID: ${user.id}");
} catch (e) {
debugPrint("❌ Storage restore failed: $e");
state = const AsyncValue.data(null);
} }
} }
///Login /// LOGIN
Future<void> login(String email, String password) async { Future<void> login(String email, String password) async {
lastAction = AuthAction.login; lastAction = AuthAction.login;
state = const AsyncValue.loading(); state = const AsyncValue.loading();
try { try {
final user = await repository.login(email, password); final user = await repository.login(email, password);
// Save user to secure storage
if (user is UserModel) { if (user is UserModel) {
await _storage.write(key: _userKey, value: user.toRawJson()); await _storage.write(key: _userKey, value: user.toRawJson());
debugPrint("✅ USER SAVED → ID: ${user.id}");
} }
state = AsyncValue.data(user); state = AsyncValue.data(user);
} catch (e, st) { } catch (e, st) {
state = AsyncValue.error(e, st); state = AsyncValue.error(e, st);
} }
} }
/// Logout /// LOGOUT
Future<void> logout() async { Future<void> logout() async {
await _storage.delete(key: _userKey); await _storage.delete(key: _userKey);
state = const AsyncValue.data(null); state = const AsyncValue.data(null);
} }
///Sign up /// SIGNUP
Future<void> signup( Future<void> signup(
String name, String name,
String email, String email,
@ -106,6 +142,7 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
) async { ) async {
lastAction = AuthAction.signup; lastAction = AuthAction.signup;
state = const AsyncValue.loading(); state = const AsyncValue.loading();
try { try {
final user = await repository.signup(name, email, password, phone); final user = await repository.signup(name, email, password, phone);
state = AsyncValue.data(user); state = AsyncValue.data(user);
@ -114,9 +151,10 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
} }
} }
///Reset password /// RESET PASSWORD
Future<void> sendPasswordResetLink(String email) async { Future<void> sendPasswordResetLink(String email) async {
state = const AsyncValue.loading(); state = const AsyncValue.loading();
try { try {
await repository.sendPasswordResetLink(email); await repository.sendPasswordResetLink(email);
state = const AsyncValue.data(null); state = const AsyncValue.data(null);
@ -125,23 +163,20 @@ class UserNotifier extends StateNotifier<AsyncValue<User?>> {
} }
} }
/// Fetch user details from backend /// FETCH FULL USER DETAILS
Future<void> getUserDetails(String userId) async { Future<void> getUserDetails(String userId) async {
state = const AsyncValue.loading(); state = const AsyncValue.loading();
try { try {
final user = await repository.getUserDetails(userId); final user = await repository.getUserDetails(userId);
// Save full details separately
await saveUserDetails(user); await saveUserDetails(user);
state = AsyncValue.data(user); state = AsyncValue.data(user);
} catch (e, st) { } catch (e, st) {
state = AsyncValue.error(e, st); state = AsyncValue.error(e, st);
} }
} }
/// Save full user details separately /// SAVE FULL DETAILS
Future<void> saveUserDetails(UserModel user) async { Future<void> saveUserDetails(UserModel user) async {
await _storage.write( await _storage.write(
key: 'logged_in_user_details', key: 'logged_in_user_details',

View File

@ -1,4 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/core/theme/app_typography.dart';
import 'package:autos/data/models/user_model.dart'; import 'package:autos/data/models/user_model.dart';
import 'package:autos/presentation/providers/user_provider.dart'; import 'package:autos/presentation/providers/user_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -10,7 +12,8 @@ class ForgotPasswordScreen extends ConsumerStatefulWidget {
const ForgotPasswordScreen({super.key}); const ForgotPasswordScreen({super.key});
@override @override
ConsumerState<ForgotPasswordScreen> createState() => _ForgotPasswordScreenState(); ConsumerState<ForgotPasswordScreen> createState() =>
_ForgotPasswordScreenState();
} }
class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> { class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
@ -51,8 +54,9 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Forgot Password"), title: Text("Forgot Password", style: AppTypo.h3),
backgroundColor: const Color(0xFF3B81F9), backgroundColor: AppTheme.primary,
iconTheme: Theme.of(context).appBarTheme.iconTheme,
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
@ -62,10 +66,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
children: [ children: [
const Text( const Text(
"Reset your password", "Reset your password",
style: TextStyle( style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
fontSize: 24,
fontWeight: FontWeight.bold,
),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text( const Text(
@ -104,7 +105,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: "Reset link sent to $email", msg: "Reset link sent to $email",
backgroundColor: Colors.green, backgroundColor: Colors.green,
toastLength: Toast.LENGTH_LONG toastLength: Toast.LENGTH_LONG,
); );
Navigator.pop(context); Navigator.pop(context);
} catch (e) { } catch (e) {
@ -115,7 +116,7 @@ class _ForgotPasswordScreenState extends ConsumerState<ForgotPasswordScreen> {
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9), backgroundColor: AppTheme.primary,
minimumSize: const Size(double.infinity, 50), minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40), borderRadius: BorderRadius.circular(40),

View File

@ -1,3 +1,5 @@
import 'package:autos/core/routing/route_paths.dart';
import 'package:autos/core/theme/app_theme.dart';
import 'package:autos/core/widgets/sso_icon_button.dart'; import 'package:autos/core/widgets/sso_icon_button.dart';
import 'package:autos/core/widgets/wave_background.dart'; import 'package:autos/core/widgets/wave_background.dart';
import 'package:autos/presentation/providers/user_provider.dart'; import 'package:autos/presentation/providers/user_provider.dart';
@ -137,16 +139,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
const Spacer(), const Spacer(),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.pushNamed(
context, context,
MaterialPageRoute( AppRoutePaths.forgotPassword,
builder: (_) => const ForgotPasswordScreen(),
),
); );
}, },
child: const Text( child: Text(
"Forgot Password?", "Forgot Password?",
style: TextStyle(color: Color(0xFF3B81F9)), style: Theme.of(context).textTheme.bodySmall,
), ),
), ),
], ],
@ -167,7 +167,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
); );
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF3B81F9), backgroundColor: AppTheme.primary,
minimumSize: Size( minimumSize: Size(
double.infinity, double.infinity,
size.height * 0.05, size.height * 0.05,

View File

@ -1,3 +1,4 @@
import 'package:autos/core/theme/app_typography.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:autos/core/widgets/hamburger_button.dart'; import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart'; import 'package:autos/core/widgets/side_menu.dart';
@ -37,10 +38,7 @@ class _EbayScreenState extends State<EbayScreen> {
child: Center( child: Center(
child: Text( child: Text(
"eBay Settings", "eBay Settings",
style: const TextStyle( style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
fontSize: 26,
fontWeight: FontWeight.w700,
),
), ),
), ),
), ),

View File

@ -0,0 +1,323 @@
import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart';
import 'package:flutter/material.dart';
import 'package:autos/core/theme/app_typography.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class PricingScreen extends ConsumerStatefulWidget {
const PricingScreen({super.key});
@override
ConsumerState<PricingScreen> createState() => _PricingScreenState();
}
class _PricingScreenState extends ConsumerState<PricingScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String selected = 'pricing';
bool isMonthly = true;
@override
Widget build(BuildContext context) {
final double topPadding = MediaQuery.of(context).padding.top + 16;
return Scaffold(
key: _scaffoldKey,
drawer: SideMenu(
selected: selected,
onItemSelected: (key) {
setState(() => selected = key);
},
),
backgroundColor: const Color(0xFFF6FDFF),
body: Stack(
children: [
// Top centered title (same style as your dashboard)
Positioned(
top: topPadding,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Choose Your Plan",
style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
),
],
),
),
// Main content
SingleChildScrollView(
padding: EdgeInsets.fromLTRB(16, topPadding + 70, 16, 20),
child: Column(
children: [
const Text(
"🚀 Subscribe to a plan and start automating your eBay listings instantly.",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 15, color: Colors.black54),
),
const SizedBox(height: 22),
// Monthly / Yearly toggle
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
border: Border.all(color: const Color(0xFF00BFFF)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_toggleButton("Monthly", isMonthly, () {
setState(() => isMonthly = true);
}),
_toggleButton("Yearly (Save 15%)", !isMonthly, () {
setState(() => isMonthly = false);
}),
],
),
),
const SizedBox(height: 30),
// Pricing cards
Column(
children: [
_pricingCard(
title: "Starter Sync",
subtitle: "Upload up to 100 products per month",
price: isMonthly ? "\$49" : "\$499",
features: const [
"Auto price & inventory updates",
"Daily sync",
"Manual sync option",
"Basic reporting dashboard",
],
isPopular: false,
),
const SizedBox(height: 20),
_pricingCard(
title: "Growth Sync",
subtitle: "Upload up to 250 products per month",
price: isMonthly ? "\$99" : "\$999",
features: const [
"Everything in Starter",
"3-hour sync interval",
"Bulk product import",
"Priority email support",
],
isPopular: true,
),
const SizedBox(height: 20),
_pricingCard(
title: "Pro Sync",
subtitle: "Upload up to 1000 products per month",
price: isMonthly ? "\$249" : "\$2499",
features: const [
"Everything in Growth",
"Real-time sync",
"Advanced analytics dashboard",
"Dedicated account manager",
"API access",
],
isPopular: false,
),
],
),
const SizedBox(height: 40),
const Divider(thickness: 0.6),
const SizedBox(height: 12),
const Text(
"© 2025. Data4Autos. All rights reserved.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: Colors.black45,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 20),
],
),
),
// Hamburger button (keeps same behavior as other screens)
HamburgerButton(scaffoldKey: _scaffoldKey),
],
),
);
}
/// Toggle button
Widget _toggleButton(String text, bool active, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: active ? const Color(0xFF00BFFF) : Colors.transparent,
borderRadius: BorderRadius.circular(30),
),
child: Text(
text,
style: TextStyle(
color: active ? Colors.white : const Color(0xFF00BFFF),
fontWeight: FontWeight.w600,
),
),
),
);
}
/// Pricing card widget
Widget _pricingCard({
required String title,
required String subtitle,
required String price,
required List<String> features,
required bool isPopular,
}) {
return Stack(
alignment: Alignment.topCenter,
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.only(top: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isPopular ? const Color(0xFF00BFFF) : Colors.transparent,
width: 2,
),
boxShadow: [
BoxShadow(
blurRadius: 12,
color: Colors.black.withOpacity(0.05),
offset: const Offset(0, 6),
),
],
),
child: Column(
children: [
const SizedBox(height: 10),
Text(
title,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
subtitle,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black54),
),
const SizedBox(height: 18),
RichText(
text: TextSpan(
children: [
TextSpan(
text: price,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const TextSpan(
text: " / month",
style: TextStyle(color: Colors.black54),
),
],
),
),
const SizedBox(height: 20),
Column(
children: features
.map(
(e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
children: [
const Icon(
Icons.check_circle,
color: Color(0xFF00BFFF),
size: 18,
),
const SizedBox(width: 10),
Expanded(child: Text(e)),
],
),
),
)
.toList(),
),
const SizedBox(height: 25),
// Subscribe button
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: isPopular
? const Color(0xFF00BFFF)
: Colors.white,
foregroundColor: isPopular
? Colors.white
: const Color(0xFF00BFFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: Color(0xFF00BFFF)),
),
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: isPopular ? 6 : 0,
),
onPressed: () {},
child: Text(
isMonthly ? "Subscribe Monthly" : "Subscribe Yearly",
),
),
),
],
),
),
// Most popular badge
if (isPopular)
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFF00BFFF),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
"MOST POPULAR",
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
],
);
}
}

View File

@ -54,7 +54,8 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
/// Filter products based on search text and stock /// Filter products based on search text and stock
List productsFilter(List products) { List productsFilter(List products) {
return products.where((product) { return products.where((product) {
final matchesSearch = _searchText.isEmpty || final matchesSearch =
_searchText.isEmpty ||
product.name.toLowerCase().contains(_searchText.toLowerCase()); product.name.toLowerCase().contains(_searchText.toLowerCase());
final matchesStock = !_inStockOnly || (product.inStock ?? true); final matchesStock = !_inStockOnly || (product.inStock ?? true);
return matchesSearch && matchesStock; return matchesSearch && matchesStock;
@ -96,7 +97,83 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
SingleChildScrollView( SingleChildScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20), padding: EdgeInsets.fromLTRB(16, topPadding + 55, 16, 20),
child: productsState.when( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// SEARCH + FILTER + COUNT ROW (always visible)
Row(
children: [
// Search bar
Expanded(
child: TextField(
onChanged: (value) {
setState(() => _searchText = value);
},
decoration: InputDecoration(
hintText: "Search products",
prefixIcon: const Icon(Icons.search),
contentPadding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.grey),
),
filled: true,
fillColor: Colors.white,
),
),
),
const SizedBox(width: 12),
// In Stock Only button
ElevatedButton(
onPressed: () {
setState(() => _inStockOnly = !_inStockOnly);
},
style: ElevatedButton.styleFrom(
backgroundColor: _inStockOnly
? Colors.green
: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"In Stock",
style: const TextStyle(fontSize: 12),
),
),
const SizedBox(width: 12),
// Count button
productsState.when(
data: (products) {
final filteredProducts = productsFilter(products);
return ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"${filteredProducts.length} products",
style: const TextStyle(fontSize: 12),
),
);
},
loading: () => const SizedBox(),
error: (_, __) => const SizedBox(),
),
],
),
const SizedBox(height: 16),
/// PRODUCTS GRID / LOADING / ERROR
productsState.when(
/// LOADING /// LOADING
loading: () => const Center( loading: () => const Center(
child: Padding( child: Padding(
@ -113,9 +190,14 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
(() { (() {
final raw = e.toString(); final raw = e.toString();
// Extract backend message if exists // Extract backend message if exists
final match = RegExp(r'message[: ]+([^}]+)').firstMatch(raw); final match = RegExp(
r'message[: ]+([^}]+)',
).firstMatch(raw);
if (match != null) return match.group(1)!.trim(); if (match != null) return match.group(1)!.trim();
return raw.replaceFirst(RegExp(r'^Exception:\s*'), ''); return raw.replaceFirst(
RegExp(r'^Exception:\s*'),
'',
);
})(), })(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red), style: const TextStyle(color: Colors.red),
@ -134,78 +216,14 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
); );
} }
// Apply search / stock filter
final filteredProducts = productsFilter(products); final filteredProducts = productsFilter(products);
return Column( return GridView.builder(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Search + Filter + Count Row
Row(
children: [
// Search bar
Expanded(
child: TextField(
onChanged: (value) {
setState(() => _searchText = value);
},
decoration: InputDecoration(
hintText: "Search products",
prefixIcon: const Icon(Icons.search),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.grey),
),
filled: true,
fillColor: Colors.white,
),
),
),
const SizedBox(width: 12),
// In Stock Only button
ElevatedButton(
onPressed: () {
setState(() => _inStockOnly = !_inStockOnly);
},
style: ElevatedButton.styleFrom(
backgroundColor: _inStockOnly ? Colors.green : Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"In Stock",
style: const TextStyle(fontSize: 12),
),
),
const SizedBox(width: 12),
// Count button
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepPurple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"${filteredProducts.length} products",
style: const TextStyle(fontSize: 12),
),
),
],
),
const SizedBox(height: 16),
/// Products Grid
GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: filteredProducts.length, itemCount: filteredProducts.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: 12, crossAxisSpacing: 12,
mainAxisSpacing: 12, mainAxisSpacing: 12,
@ -270,11 +288,11 @@ class _ProductsScreenState extends ConsumerState<ProductsScreen> {
), ),
); );
}, },
),
],
); );
}, },
), ),
],
),
), ),
/// Hamburger Button /// Hamburger Button

View File

@ -1,4 +1,5 @@
import 'package:autos/core/routing/route_paths.dart'; import 'package:autos/core/routing/route_paths.dart';
import 'package:autos/core/theme/app_typography.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:autos/core/widgets/hamburger_button.dart'; import 'package:autos/core/widgets/hamburger_button.dart';
import 'package:autos/core/widgets/side_menu.dart'; import 'package:autos/core/widgets/side_menu.dart';
@ -34,10 +35,10 @@ class _StoreScreenState extends State<StoreScreen> {
top: topPadding, top: topPadding,
left: 0, left: 0,
right: 0, right: 0,
child: const Center( child: Center(
child: Text( child: Text(
"eBay Locations", "eBay Locations",
style: TextStyle(fontSize: 26, fontWeight: FontWeight.w700), style: AppTypo.h2.copyWith(fontWeight: FontWeight.w700),
), ),
), ),
), ),